Build Tools: Maven & Gradle
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against GWT 2.13.1 (2026-06-19), gwt-maven-plugin 1.3.0 (2026-05-31), Maven 3.9.16, source level 17 and
Jakarta Servlet 6.0 (the values the modular-webapp archetype 2026.6.22 generates), as of 2026-10-08. Every Maven command and
every console output shown was run on that stack with JDK 21 (the same build also passes on JDK 26), except the Jetty command of the
multi-module section, which was run on the scratch application earlier in the work on this section, and the CI workflow fragment. It is build and tooling
material: nothing here is compiled to JavaScript, but the POM decides which of your sources are, which artifacts reach the
server, and how the compiler is invoked. The compiler flags themselves belong to the Compiler & Compilation page,
the module XML to Project Structure & Modules, and the run loop to Development Mode & Debugging; this page covers the
build around them.
What the build has to do
A GWT application is three kinds of code that the Java tooling normally mixes together, and the build exists to keep them apart:
-
client code is compiled twice: once by
javac(so the IDE and unit tests work) and once by the GWT compiler into JavaScript. The GWT compiler needs the Java sources of everything the client uses, not just class files; -
shared code (DTOs, RPC interfaces) is compiled by
javacfor the server and, with its sources, by the GWT compiler for the client; -
server code is ordinary Java for a servlet container and must never contain client-only classes, nor the other way round.
jar plus sources jar"] --> client["bookshelf-client
packaging gwt-app"] shared --> server["bookshelf-server
packaging war"] widgets["bookshelf-widgets
packaging gwt-lib"] --> client client -->|compiled JavaScript| warapp["gwt-app war
only compiler output"] warapp -->|overlay| server server --> deploy["bookshelf-server.war"]
The running scenario of this section, the Bookshelf, is exactly this shape: bookshelf-shared, bookshelf-client and
bookshelf-server as generated by the net.ltgt.gwt.archetypes:modular-webapp archetype, plus a small bookshelf-widgets library module
added on this page to show a gwt-lib. The sources a GWT compile reads come from the module XML <source> paths, so the build must
make the sources of dependencies available on the compiler classpath. gwt-maven-plugin does this with two packagings (gwt-lib
and gwt-app) and a set of goals, described next.
The org.gwtproject BOM and the GWT artifacts
Since 2.10.0 GWT publishes under the Maven groupId org.gwtproject (release notes 2.10.0); the former com.google.gwt coordinates stop at
version 2.10.0. The bill of materials org.gwtproject:gwt:2.13.1 is a pom-type artifact that manages the versions of every GWT
artifact, so that your POM states the GWT version exactly once:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.gwtproject</groupId>
<artifactId>gwt</artifactId>
<version>2.13.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
</dependency>
</dependencies>
</dependencyManagement>
Source: gwt-maven-plugin — Basic usage
Reading the 2.13.1 BOM (gwt-2.13.1.pom on Maven Central) gives the following picture. The version column is what the BOM pins; the
last column says where the artifact belongs in the Bookshelf.
| Artifact | What it contains | Where it belongs |
|---|---|---|
|
The client libraries (widgets, RPC client side, i18n, the |
|
|
The compiler, DevMode and the test runner ( |
|
|
The Super Dev Mode code server. |
Not declared in the Bookshelf POMs, and |
|
The server-side runtime ( |
Servers on Servlet 3.1 / 4. |
|
The same runtime for the |
|
|
RequestFactory: |
Only if you use RequestFactory. |
|
The JsInterop annotations, pinned to 2.0.0 by the BOM (2.1.0 exists on Maven Central). |
Arrives transitively with |
|
Kept in the BOM, according to a comment in the POM, so that build tools can relocate the old groupId correctly. |
Do not depend on them. |
The BOM also pins third-party libraries that the tools use (gson, htmlunit, ASM, Jetty 9.4.58, icu4j, JDT), which is why
they appear in a dependency:tree of any module that declares gwt-dev. A real excerpt, from the bookshelf-widgets module of
this page (library with gwt-user at compile scope and gwt-dev at provided scope):
[INFO] com.example.bookshelf:bookshelf-widgets:gwt-lib:1.0-SNAPSHOT
[INFO] +- org.gwtproject:gwt-user:jar:2.13.1:compile
[INFO] | +- com.google.jsinterop:jsinterop-annotations:jar:2.0.0:compile
[INFO] | +- javax.validation:validation-api:jar:1.0.0.GA:compile
[INFO] | +- javax.validation:validation-api:jar:sources:1.0.0.GA:compile
[INFO] | +- javax.servlet:javax.servlet-api:jar:3.1.0:compile
[INFO] | \- org.w3c.css:sac:jar:1.3:compile
[INFO] \- org.gwtproject:gwt-dev:jar:2.13.1:provided
[INFO] +- com.google.code.findbugs:jsr305:jar:1.3.9:provided
[INFO] +- com.google.code.gson:gson:jar:2.11.0:provided
Two consequences matter in practice:
-
gwt-userbringsjavax.servlet-api3.1.0 even when your server is Jakarta. Keep the real server API (jakarta.servlet:jakarta.servlet-api,provided) in the server module, usegwt-servlet-jakartathere, and never putgwt-useron the server classpath of a deployed WAR: it duplicatesgwt-servlet. -
gwt-devis large (a complete Jetty and HtmlUnit). Agwt-appmodule can declare it at the default scope because thegwt-appWAR contains only the compiler output, but a library should declare itprovided, otherwise every consumer inherits it.
gwt-maven-plugin
The Maven integration used by the archetypes, by the GWT project’s own samples (moved to Maven in 2.13.0) and by this section is
net.ltgt.gwt.maven:gwt-maven-plugin, written by Thomas Broyer. Its design goals are stated on its site: it is independent of the
GWT version (it passes explicit argument lists to GWT instead of modelling every flag), it understands multi-module projects, and it
adds two packagings and two artifact types. It is tested against the latest two stable GWT versions and the latest snapshot.
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.3.0</version>
<extensions>true</extensions>
<configuration>
<moduleName>com.example.bookshelf.App</moduleName>
<moduleShortName>bookshelf</moduleShortName>
<sourceLevel>17</sourceLevel>
<failOnError>true</failOnError>
</configuration>
</plugin>
Source: gwt-maven-plugin — Basic usage
<extensions>true</extensions> is what makes the gwt-app and gwt-lib packagings (and artifact types) known to Maven; without it
Maven answers Unknown packaging: gwt-app. The usage page still shows BOM 2.13.0 while the plugin 1.3.0 is the latest release; use the BOM
version you actually run. failOnError makes the compile fail when sources have errors instead of producing a partial result, and the
generated POMs of both the archetype and webAppCreator set it. sourceLevel is explained under
the compile goal.
The default project layout, which every goal assumes unless you reconfigure it:
| Path | Use |
|---|---|
|
Hand-written module template, processed by |
|
Standard Java sources and resources; GWT also reads them as sources, so |
|
GWT super-sources, added by |
|
Standard test sources; |
|
Test super-sources, added by |
Packagings and lifecycles
The plugin contributes two lifecycles, each triggered by a new <packaging>. They look like the jar and war lifecycles with
different default bindings, and the bindings below are exactly those of the plugin’s Lifecycles page.
enforce-encoding, add-super-sources,
add-test-super-sources"] --> l2["generate-resources
generate-module,
generate-module-metadata"] l2 --> l3["compile and test-compile
javac"] --> l4["test
gwt:test"] --> l5["package
package-lib"] end subgraph app["packaging gwt-app"] direction LR a1["initialize
same three goals"] --> a2["generate-resources
generate-module"] a2 --> a3["compile and test-compile
javac"] --> a4["test
gwt:test"] --> a5["prepare-package
gwt:compile"] --> a6["package
package-app"] end
| Phase | gwt-lib |
gwt-app |
|---|---|---|
initialize |
|
the same three goals |
generate-resources |
|
|
process-resources, compile, process-test-resources, test-compile |
|
the same (no |
test |
|
|
prepare-package |
none |
|
package |
|
|
install, deploy |
|
|
You can see the bindings working in a real run. This is mvn package -DskipTests for the bookshelf-widgets (gwt-lib) module;
the lines that are not GWT goals are omitted:
[INFO] ------------------------------[ gwt-lib ]-------------------------------
[INFO] --- gwt:1.3.0:enforce-encoding (default-enforce-encoding) @ bookshelf-widgets ---
[INFO] --- gwt:1.3.0:add-super-sources (default-add-super-sources) @ bookshelf-widgets ---
[INFO] --- gwt:1.3.0:add-test-super-sources (default-add-test-super-sources) @ bookshelf-widgets ---
[INFO] --- gwt:1.3.0:generate-module (default-generate-module) @ bookshelf-widgets ---
[INFO] --- gwt:1.3.0:generate-module-metadata (default-generate-module-metadata) @ bookshelf-widgets ---
[INFO] --- compiler:3.15.0:compile (default-compile) @ bookshelf-widgets ---
[INFO] --- gwt:1.3.0:test (default-test) @ bookshelf-widgets ---
[INFO] --- gwt:1.3.0:package-lib (default-package-lib) @ bookshelf-widgets ---
[INFO] Building jar: .../bookshelf-widgets/target/bookshelf-widgets-1.0-SNAPSHOT.jar
The gwt-app module of the same build shows gwt:compile running at prepare-package and the output being packaged:
[INFO] ------------------------------[ gwt-app ]-------------------------------
[INFO] --- gwt:1.3.0:enforce-encoding (default-enforce-encoding) @ bookshelf-client ---
[INFO] --- gwt:1.3.0:add-super-sources (default-add-super-sources) @ bookshelf-client ---
[INFO] --- gwt:1.3.0:add-test-super-sources (default-add-test-super-sources) @ bookshelf-client ---
[INFO] --- gwt:1.3.0:generate-module (default-generate-module) @ bookshelf-client ---
[INFO] --- gwt:1.3.0:test (default-test) @ bookshelf-client ---
[INFO] --- gwt:1.3.0:compile (default-compile) @ bookshelf-client ---
[INFO] Compiling module com.example.bookshelf.App
[INFO] Compiling 6 permutations
[INFO] Compilation succeeded -- 15,479s
[INFO] Linking into .../bookshelf-client/target/bookshelf-client-1.0-SNAPSHOT/bookshelf
[INFO] --- gwt:1.3.0:package-app (default-package-app) @ bookshelf-client ---
[INFO] Building war: .../bookshelf-client/target/bookshelf-client-1.0-SNAPSHOT.war
The two artifact types
The same two names are also dependency types. They describe what the artifact contains, and therefore how a consumer may use it.
| Type | Content | How to depend on it |
|---|---|---|
|
A JAR with both compiled classes and their sources: the generated module descriptor ( |
|
|
A WAR holding only the compiler output: |
|
The entries of bookshelf-client-1.0-SNAPSHOT.war in this run confirm the second row: a manifest, the permutation script
bookshelf/342AE3F7D9EDD9BB635FB4F8BFDC783C.cache.js, bookshelf/bookshelf.devmode.js, the serialization policy
bookshelf/85B004BBB10BADC9964AD6DE9572205E.gwt.rpc and the theme images. The server module reaches the same files through the overlay:
bookshelf-server-1.0-SNAPSHOT.war contains bookshelf/bookshelf.nocache.js (checked with unzip -l).
The goals
The plugin has twelve goals (the plugin-info page and mvn gwt:help agree; help is the standard Maven plugin-plugin goal).
mvn net.ltgt.gwt.maven:gwt-maven-plugin:1.3.0:help prints This plugin has 12 goals, and with -Ddetail=true -Dgoal=<name> the
parameters of one goal.
| Goal | Default phase | Purpose |
|---|---|---|
|
initialize |
Enforces that the source encoding is UTF-8. No parameters. |
|
initialize |
Adds the super-source directory to the project resources. |
|
initialize |
The same for the test resources. |
|
generate-resources |
Generates the GWT module descriptor from the template and the dependencies. |
|
generate-resources |
Writes |
|
prepare-package |
Invokes the GWT compiler (production mode). |
|
test |
Runs |
|
package |
Packages the compiled application into a WAR-like archive. |
|
package |
Packages the library into a JAR. |
|
none (aggregator) |
Runs the Super Dev Mode code server. |
|
none (aggregator) |
Runs DevMode, for standalone applications. |
|
none |
Displays help for the plugin; does not need a project. |
Source: gwt-maven-plugin — Goals
gwt:codeserver and gwt:devmode are aggregator goals that require dependency resolution at compile+runtime scope and first execute
the process-classes phase of the modules they run; the other goals resolve compile (compile), runtime (generate-module) or test
(test) dependencies.
The compile goal
gwt:compile runs the GWT compiler in a forked JVM. Its parameters (every one of them is documented on the goal page; the descriptor
inside the 1.3.0 JAR gives the defaults used here) are:
| Parameter | Property or default | Meaning |
|---|---|---|
|
none |
The module to compile, for example |
|
none |
The short name used for the output directory and the |
|
|
Java source level passed to the compiler ( |
|
|
Script output style: |
|
|
Optimisation level, 0 (none) to 9 (maximum). |
|
|
Faster, less optimised compilations. |
|
|
Number of local workers compiling permutations; a number followed by |
|
|
Succeed only if no input files have errors. |
|
|
Level of logging detail. |
|
list |
Additional arguments passed to the compiler, in the order given (ordering matters for some GWT flags). |
|
none |
Arguments for the forked JVM (for example |
|
map |
System properties passed to the compiler. |
|
|
The target web application directory. |
|
|
The compiler work directory, the directory for deployable but not servable files, and the directory for extra files not intended for deployment. |
|
|
Skips the compiler. Documented use: quickly package an incomplete or stale application that is used as a dependency (an overlay) in a WAR. |
|
|
Compiles even if the output looks up to date. |
|
|
Granularity in milliseconds of the last-modified comparison used for the up-to-date check. |
Source: gwt-maven-plugin — gwt:compile
A few behaviours deserve a real transcript. Compilation is skipped when the output is up to date: running the same build a second time took 0.9 seconds in total (the first one took 17 seconds, six permutations):
$ mvn -B package -DskipTests -pl bookshelf-client -am
[INFO] --- gwt:1.3.0:compile (default-compile) @ bookshelf-client ---
[INFO] Compilation output seems uptodate. GWT compilation skipped.
[INFO] --- gwt:1.3.0:package-app (default-package-app) @ bookshelf-client ---
[INFO] BUILD SUCCESS
[INFO] Total time: 0.946 s
With -Dgwt.skipCompilation=true the goal only logs GWT compilation is skipped and the application WAR is built without compiler
output. With the properties of the table you steer a single run from the command line, here a draft compile with readable output and
two workers, forced because the output was already up to date:
mvn -B package -DskipTests -pl bookshelf-client -am \
-Dgwt.draftCompile=true -Dgwt.style=PRETTY -Dgwt.localWorkers=2 -Dgwt.forceCompilation=true
That run finished in 11.3 seconds for the whole reactor, against 18.7 seconds for the first full build (a draft compile also turns the -Xnamespace default to
PACKAGE, hence the new warnings Namespace option is not compatible with CodeSplitter, turning it off, which can be ignored).
Which sourceLevel is used is the most frequent surprise. The property it reads is maven.compiler.source, and
maven-compiler-plugin projects that only set maven.compiler.release (the modern way) leave it empty. Both generated POMs therefore
set <sourceLevel> explicitly: the archetype uses 17 together with <release>17</release> on the compiler plugin, and the
webAppCreator Maven template uses maven.compiler.release 11 with a comment explaining that the compiler of 2.12.0 and later needs level 11 or above. The Compiler
-help text and the Developer Guide are right that the command-line compiler’s own default is 1.8 (the default argument registered in ToolBase), so Java 12 to 17 syntax needs an explicit -sourceLevel 17, and auto resolves to the level of the running JVM. The plugin’s <sourceLevel> is therefore not the compiler’s default but whatever maven.compiler.source holds; setting the level
explicitly avoids depending on either (details on the Compiler & Compilation page).
Further compiler flags go in compilerArgs. The Bookshelf enables the generation of JsInterop exports this way:
<configuration>
<moduleName>com.example.bookshelf.App</moduleName>
<moduleShortName>bookshelf</moduleShortName>
<compilerArgs>
<arg>-generateJsInteropExports</arg>
</compilerArgs>
</configuration>
The code server and DevMode goals
gwt:codeserver is the Maven way to run Super Dev Mode. It is an aggregator goal, so its configuration belongs in the root
POM, and it gathers the gwt-app modules of the reactor by itself. The plugin documentation lists its differences from gwt:compile:
-
the applications and all reactor modules depended on with
<type>gwt-lib</type>or<classifier>sources</classifier>have their source roots used as they are, so edits are seen immediately (a resource directory with filtering or relocation behaves differently); -
the dependency scope for the classpath is
runtimeby default rather thancompile, configurable withclasspathScope; -
since GWT 2.7 you should set
launcherDir, a folder your web server or servlet container reads static resources from, so that the*.nocache.jswritten by the code server is loadable and the browser’s "recompile on reload" works; -
because the goal triggers
process-classesin all modules, restrict the reactor with-pland add-am, or setprojects(-Dgwt.projects=) in a profile; -
because the code server takes different arguments from the compiler, arguments needed by both are defined twice, in
compilerArgsand incodeserverArgs.
| Parameter | Property or default | Meaning |
|---|---|---|
|
|
Where the launcher files ( |
|
|
Work directory (must be writable). |
|
|
Dependency scope ( |
|
|
Comma-delimited modules to run; defaults to the module names discovered from |
|
|
Comma-delimited reactor projects to run; defaults to all |
|
list |
Additional arguments for the code server. |
|
|
As for |
|
none |
The forked JVM, as for |
The archetype’s root POM holds the configuration that makes the aggregator goal work, with <inherited>false</inherited> so that it
applies to the root only. This is the Bookshelf version, with a codeserverArgs entry added that moves the code server to port 9951
(the default is 9876) so that it cannot clash with another instance:
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<inherited>false</inherited>
<configuration>
<launcherDir>${basedir}/bookshelf-server/target/gwt/launcherDir</launcherDir>
<codeserverArgs>
<arg>-port</arg>
<arg>9951</arg>
</codeserverArgs>
</configuration>
</plugin>
Running it with the reactor restricted to the client and its dependencies (the log is shortened):
$ mvn gwt:codeserver -pl bookshelf-client -am
[INFO] --- gwt:1.3.0:codeserver (default-cli) @ bookshelf ---
[INFO] Turning off precompile in incremental mode.
[INFO] Super Dev Mode starting up
[INFO] workDir: .../target/gwt/codeserver
[INFO] Loading Java files in com.example.bookshelf.App.
[INFO] Module setup completed in 5285 ms
[INFO] The code server is ready at http://127.0.0.1:9951/
GET /recompile-requester/bookshelf answered 200 on that port. The goal executes at the root project (@ bookshelf), not in the client
module, which is the visible sign of an aggregator. The archetype README and some guides still write the wildcard form -pl *-client, and it does not work:
Maven 3.9.16 stops with Could not find the selected project in the reactor: *-client; wildcards are a Maven 4 feature. Use
-pl bookshelf-client or -pl :bookshelf-client.
gwt:devmode is the same idea for the classic DevMode and for standalone applications, that is, client-only code (offline
applications, or applications talking to a remote server through CORS) for which a code server serving a host page from elsewhere is
less comfortable. Its extra parameters are devmodeArgs, devmodeWorkDir (default target/gwt/devmode/work), warDir (default
target/gwt/devmode/war, mandatory but with a default; roughly the counterpart of launcherDir) and startupUrls, a list of URLs to open
automatically that should point to a host page in the module’s public path. The plugin documentation discourages using it with a
web application (it needs war:exploded first) because the plugin documentation considers the webapp class loader inside DevMode unreliable. The 2.13.0 release
notes note that DevMode now serves only static files by default, ; the Bookshelf uses gwt:codeserver.
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<configuration>
<warDir>${project.build.directory}/gwt/devmode/war</warDir>
<startupUrls>
<startupUrl>index.html</startupUrl>
</startupUrls>
</configuration>
</plugin>
The test goal
gwt:test runs tests that extend GWTTestCase (see Testing) with the setup they need (the GWT test runner, HtmlUnit by default). It is built on
Surefire, so most parameters (includes, excludes, test, skipTests, forkCount, argLine, groups, reportsDirectory
and so on) behave as in Surefire, and the plugin warns that part of its documentation is inherited from Surefire and may be wrong or irrelevant. The
GWT-specific ones are:
| Parameter | Property or default | Meaning |
|---|---|---|
|
list |
Additional arguments for the |
|
list, default false |
Compiler arguments, and whether they are prepended to |
|
|
TCP port of the embedded web server and of the code server (both pick a free port by default). |
|
|
Output directories. |
|
as for |
Compilation of the tests' module. |
|
|
JVM assertions are on by default for the tests, which activates GWT’s internal consistency checks (see the permutation pitfall below). |
|
list |
Test file patterns. |
|
default |
The JUnit artifact the runner looks for. |
Source: gwt-maven-plugin — gwt:test
The pitfalls found while running the Bookshelf tests are important enough to show verbatim. First, in this setup the goal ran no tests
at all without an explicit <includes>, and printed nothing about it (the lifecycle output for the module was just the goal header
followed by the next goal). The archetype sets an explicit <includes><include>**/*Test.java</include></includes> for that reason:
<configuration>
<moduleName>com.example.bookshelf.App</moduleName>
<includes>
<include>**/*Test.java</include>
</includes>
</configuration>
Second, -Dtest= is applied to every module of the reactor, and a module without a matching test fails:
$ mvn verify -pl bookshelf-client -am -Dtest=BookshelfGwtTest
[ERROR] Failed to execute goal net.ltgt.gwt.maven:gwt-maven-plugin:1.3.0:test (default-test) on project bookshelf-widgets:
No tests matching pattern "BookshelfGwtTest" were executed! (Set -DGWT tests.failIfNoSpecifiedTests=false to ignore this error.)
Source: gwt-maven-plugin — gwt:test test
The hint in the message has a typo; the property from the descriptor is surefire.failIfNoSpecifiedTests:
$ mvn verify -pl bookshelf-client -am -Dtest=BookshelfGwtTest -Dsurefire.failIfNoSpecifiedTests=false
[INFO] --- gwt:1.3.0:test (default-test) @ bookshelf-client ---
[INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider
[INFO] T E S T S
[INFO] Running com.example.bookshelf.client.BookshelfGwtTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.05 s -- in com.example.bookshelf.client.BookshelfGwtTest
Source: gwt-maven-plugin — gwt:test test
Third, use mvn verify (or install), not mvn test, for a client module whose shared dependency is consumed through its sources
classifier JAR: that JAR is only produced at package, and mvn test -pl bookshelf-client -am fails with
No source code is available for type com.example.bookshelf.shared.Book; did you forget to inherit a required module?. A test with
several permutations also fails under -ea in com.google.gwt.dev.Permutation.assertSameAnswers unless the test module pins a single
locale and user.agent (the dedicated test modules of the Bookshelf do that); the Testing page covers the
test side.
Module generation goals
gwt:generate-module is what lets a Maven project avoid maintaining <inherits> lines by hand. It reads the template
(src/main/module.gwt.xml by default) and writes the real descriptor to the class output, in the package of moduleName. The
description in the descriptor is precise:
-
META-INF/gwt/mainModulefiles from the project’s direct dependencies (not transitive) produce<inherits>directives, inserted at the very beginning of the generated module, before any<inherits>of the template; -
if
moduleShortNameis given, it overwrites anyrename-toof the template; -
unless the template contains a
<source>or<super-source>element, the lines<source path="client"/>,<source path="shared"/>and<super-source path="super"/>are appended at the end.
| Parameter | Default | Meaning |
|---|---|---|
|
none |
The module to generate (both goals). |
|
none |
Used for |
|
|
Module definition to merge with. A missing file is the same as an empty one. |
|
|
Where the descriptor, respectively |
|
true |
Generate |
|
false |
Disable generation in favour of a hand-written descriptor (see the migration section). |
|
false |
Disable generation of |
The effect is visible in the Bookshelf. The bookshelf-widgets library declares com.example.bookshelf.widgets.Widgets as its module
name. Its template (the whole file) is:
<module>
<inherits name="com.google.gwt.user.User"/>
<source path="client"/>
</module>
The build wrote the descriptor to com/example/bookshelf/widgets/Widgets.gwt.xml (identical to the template, since there is no short name
and no dependency with a mainModule), and the one-line metadata file META-INF/gwt/mainModule containing the module name. After
bookshelf-client declared a dependency on that library, the generated target/classes/com/example/bookshelf/App.gwt.xml of the client
starts with:
<module rename-to="bookshelf">
<inherits name="com.example.bookshelf.widgets.Widgets"/>
<inherits name="com.google.gwt.user.User"/>
<inherits name="com.google.gwt.user.theme.clean.Clean"/>
The new <inherits> line was never written by hand, and the rename-to="bookshelf" came from moduleShortName. That is also the reason
for the order rule: the generated inherits come before the template’s, so a template that depends on the order of inheritance (for
overrides, for example) must say so with its own explicit line.
Super-source goals
gwt:add-super-sources adds src/main/super (parameter superSourceDirectory) to the resources, and gwt:add-test-super-sources adds
src/test/super (testSuperSourceDirectory). GWT requires super-sources to live in a subdirectory of the module; with
relocateSuperSource (respectively relocateTestSuperSource) set to true the plugin relocates the directory’s content into a super
subfolder of the module named by moduleName, and the <super-source path="super"/> line from the generation rules above then makes
the sources visible. Both parameters default to false. The two goals are bound to initialize; the Bookshelf has no super-sources, which
the build reports as skip non existing resourceDirectory .../src/main/super.
<configuration>
<moduleName>com.example.bookshelf.widgets.Widgets</moduleName>
<superSourceDirectory>src/main/super</superSourceDirectory>
<relocateSuperSource>true</relocateSuperSource>
</configuration>
Packaging goals
gwt:package-app assembles the gwt-app WAR from the compiler output, and gwt:package-lib the gwt-lib JAR.
| Parameter | Default | Meaning |
|---|---|---|
|
|
Name of the generated WAR. |
|
|
Where the archive is written. |
|
|
Directory whose classes and resources go into the JAR. |
|
|
Name of the JAR. |
|
|
Rebuild even if the contents appear unchanged. By default an existing archive is kept when inputs did not change; this does not work if another plugin (such as the shade plugin) post-processes the archive. |
|
none |
A Maven Archiver configuration. |
The listing of the library JAR built in this run shows what gwt-lib means. It has the sources next to the classes, the generated
module and the metadata:
META-INF/MANIFEST.MF
META-INF/gwt/mainModule
com/example/bookshelf/widgets/Widgets.gwt.xml
com/example/bookshelf/widgets/client/StockBadge.java
com/example/bookshelf/widgets/client/StockBadge.class
META-INF/maven/com.example.bookshelf/bookshelf-widgets/pom.xml
META-INF/maven/com.example.bookshelf/bookshelf-widgets/pom.properties
Multi-module builds
The three-module split is not decoration: it is how the plugin keeps the three code kinds apart. The Bookshelf reactor, in the order
Maven resolves it from the dependencies, is bookshelf-widgets (gwt-lib), bookshelf-shared (jar), bookshelf-client (gwt-app) and
bookshelf-server (war). The relationships to set up are:
<!-- bookshelf-client/pom.xml -->
<packaging>gwt-app</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>bookshelf-shared</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>bookshelf-shared</artifactId>
<version>${project.version}</version>
<classifier>sources</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>bookshelf-widgets</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.gwtproject</groupId>
<artifactId>gwt-user</artifactId>
</dependency>
<dependency>
<groupId>org.gwtproject</groupId>
<artifactId>gwt-dev</artifactId>
</dependency>
</dependencies>
The shared module needs a sources classifier JAR (the client module lists it as a second dependency) because the compiler reads Java
source; the maven-source-plugin execution jar-no-fork bound to package in the root pluginManagement produces it. A gwt-lib
needs none of that because its JAR already contains sources, which is why a pure client library should be a gwt-lib and a library
shared with the server should stay a plain JAR with sources.
The server module depends on the client’s WAR (<type>war</type>, runtime scope) and the maven-war-plugin overlays it:
<!-- bookshelf-server/pom.xml, profile env-prod (active by default) -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>bookshelf-client</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
The archetype wraps that dependency in a profile pair. env-prod is active by default and adds the client WAR; env-dev is activated by
-Denv=dev (or -Penv-dev, as the archetype README says) and instead points the Jetty plugin at the launcherDir written by the code server, so the server can be started
without compiling the application. The comment in the generated POM explains the odd shape: Maven forces an explicit env-prod profile
to be able to exclude the dependency in env-dev. The effect, measured on the Bookshelf, was that the server WAR built with -Denv=dev
contained no nocache.js (0 matches), while the default build contained one:
# Development: the code server runs in one terminal, the server in another
mvn gwt:codeserver -pl bookshelf-client -am
mvn -Denv=dev -pl bookshelf-server -am org.eclipse.jetty.ee10:jetty-ee10-maven-plugin:12.1.10:run
# Production: compiles the client and overlays it
mvn clean package
The reactor options to remember are -pl (select projects), -am (also make the projects they depend on), -amd (also make the
projects depending on them) and -rf (resume from). The first command above works because -pl bookshelf-client -am selects the
client, the shared module and the library, and because the aggregator goal runs once at the root. In a shell, use plain project
names, as noted before.
Skipping GWT work in CI profiles
Compiling GWT is the slowest step of a CI job (17 seconds for the small Bookshelf, minutes for a real application), and most jobs do not need it: a server-only test run, a lint job, or a documentation job. Because the plugin parameters are bound to properties, a profile can switch work off without editing any plugin configuration.
<profiles>
<profile>
<id>no-gwt</id>
<properties>
<gwt.skipCompilation>true</gwt.skipCompilation>
<skipTests>true</skipTests>
</properties>
</profile>
<profile>
<id>ci-fast</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<configuration>
<draftCompile>true</draftCompile>
<style>PRETTY</style>
<localWorkers>2</localWorkers>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
</profiles>
Run with -Pno-gwt the whole reactor built in 3.4 seconds, all four modules SUCCESS, with the log lines GWT compilation is skipped for the
client and Tests are skipped. for each module. That is the right profile for a job that only needs to know that the Java sources
compile. The ci-fast profile keeps the compile but cuts its cost; it is suitable for pull-request builds that run the browser tests,
while the release build runs without it so that the shipped JavaScript is optimised and obfuscated. Guidelines that follow from
the descriptor:
-
Prefer properties for toggles (
gwt.skipCompilation,gwt.draftCompile,gwt.style,gwt.localWorkers) and profiles for presets. The plugin has few properties by design because it passes arguments to GWT explicitly; the migration page of the plugin suggests presets in profiles and the attributecombine.children="append"to extend an existingcompilerArgslist from a profile. -
gwt.localWorkersaccepts a number or a number withC:-Dgwt.localWorkers=0.5Cuses half the cores, useful on shared CI runners. -
Do not set
skipCompilationfor the build that produces the artifact you deploy: agwt-appWAR built that way is valid but empty of JavaScript, and the server WAR overlays it without complaint. -
The plugin decides that a compile can be skipped by checking that the output
*.nocache.jsexists and that the POM, sources and dependencies have not changed (the documented behaviour offorceCompilation). For a release build, pass-Dgwt.forceCompilation=trueso that a staletarget/directory restored from a CI cache can never ship. -
GWT tooling needs Java 11 or newer to run (the 2.13.0 notes say that 2.13 is probably the last release whose compiler and dev tools run on Java 11), so pin the CI JDK to a version your GWT release supports and set
sourceLevelexplicitly.
# GitHub Actions job (fragment): server tests on every push, full GWT build only on the main branch
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
cache: maven
- run: mvn -B -Pno-gwt verify
- run: mvn -B clean verify
if: github.ref == 'refs/heads/main'
The Maven commands of the fragment were run as shown; the workflow file itself was not executed.
Packaging reusable GWT libraries as JARs
A library that other GWT projects will inherit must ship its sources, because the consumer’s compiler translates them. The module
descriptor and any public resources must be on the classpath next to them. Doing this by hand means placing src/main/java in the
resources (or building a second JAR with the maven-source-plugin and a sources classifier, as the shared module does) and
writing the .gwt.xml correctly. The plugin automates the first variant, which is why a gwt-lib is the recommended packaging for
client-only libraries:
-
set
<packaging>gwt-lib</packaging>; -
set
<moduleName>to the fully qualified module the library exposes; -
keep a
src/main/module.gwt.xmltemplate with the<inherits>of the other GWT libraries and the<source>paths if they are not the defaults (the plugin appendsclient,sharedandsupersource paths when the template has none); -
declare
gwt-usernormally andgwt-devasprovided; -
install or deploy it: the consumer depends on it with the usual
<dependency>(the type defaults tojar, which is accepted).
The consumer needs no <inherits> line when it uses gwt:generate-module, because META-INF/gwt/mainModule from the direct dependency
generates it, and it needs one if the module template is hand-written and generation is skipped. A library that is not a gwt-lib but
publishes a sources classifier is consumed as in the Bookshelf’s bookshelf-shared: both the binary and the sources dependency, and
<inherits> of the module (or the mainModule mechanism when the producer uses gwt:generate-module-metadata).
What goes wrong is nearly always one of two things, both visible as compiler errors: the sources are not on the classpath (No source
code is available for type com.example.bookshelf.shared.Book; did you forget to inherit a required module?), or the module is not
inherited. The next pitfall table collects them. The module XML itself (the <source>, <public> and <inherits> elements and the
standard module hierarchy) is explained on the Project Structure & Modules page; the official reference is the
Developer Guide chapter on modules.
The legacy Codehaus plugin and migrating from it
Before Thomas Broyer’s plugin, the Maven plugin for GWT was org.codehaus.mojo:gwt-maven-plugin (goal prefix gwt, the same prefix, which
makes it easy to confuse the two). Its last release is 2.10.0 (the Maven Central metadata was last updated on 2022-06-25), and its build properties
show it was built against GWT 2.10.0 (gwt.version). It is tied to one GWT version, which is the root of most migration work. Its
goals, read from the plugin.xml of the 2.10.0 JAR:
| Goal | Default phase | Purpose (descriptor) |
|---|---|---|
|
prepare-package |
Invokes the GWT compiler. |
|
none |
Runs the project in GWT classic or Super Dev Mode. |
|
none |
Runs GWT modules with Super Dev Mode. |
|
none |
Runs with a debugger port hook, optionally suspended. |
|
integration-test |
Mimics Surefire to run `GWTTestCase`s. |
|
process-resources, package |
Copy GWT sources and the module descriptor as resources, respectively into the project JAR. |
|
process-resources |
Merges GWT servlet elements into the deployment descriptor. |
|
generate-sources |
Generates the |
|
generate-sources |
Generates i18n interfaces from properties files, and CSS interfaces from CSS files. |
|
none |
Housekeeping, the compile report, Eclipse launch configurations. |
Goals such as generateAsync, i18n, css, mergewebxml and eclipse have no equivalent in the new plugin; the plugin’s migration page
says that most goals of the old plugin have none. The differences it lists are:
| Codehaus (Mojo) plugin | net.ltgt.gwt.maven plugin |
|---|---|
Tied to one GWT version through its own dependencies |
Independent of the GWT version. You must add the dependencies |
The module is wherever you keep it ( |
The module is managed in |
|
The only goals with equivalents: |
One project holds the whole application |
|
Dozens of per-flag parameters (for example |
Few properties. Arguments are passed explicitly through list properties ( |
A practical migration order that keeps the project building at every step:
-
Replace the plugin coordinates and set
<extensions>true</extensions>; keep the module descriptor where it is by settingskipModuleto true (the lowest-risk route) and move tosrc/main/module.gwt.xmllater. -
Import the
org.gwtproject:gwtBOM and declaregwt-userandgwt-dev; remove the version properties of the old plugin that selected the GWT version. -
Move the old
<configuration>entries intomoduleName,moduleShortName,sourceLevel,style,draftCompile,jvmArgsandcompilerArgs. -
Replace
gwt:run-codeserverbygwt:codeserverat the root of a multi-module build, withlauncherDir. -
Replace
gwt:source-jarandgwt:resourcesby thegwt-libpackaging for client libraries. -
Split the server out into its own module and overlay the
gwt-appWAR on it. -
Delete the use of
gwt:i18n,gwt:css,gwt:generateAsync,gwt:mergewebxmland write what they generated.
A concrete failure reproduces the skipModule pitfall. When the library module had skipModule set to true but no descriptor under
target/classes, gwt:codeserver at the root failed with:
[INFO] Loading inherited module 'com.example.bookshelf.App'
[INFO] Loading inherited module 'com.example.bookshelf.widgets.Widgets'
[INFO] [ERROR] Unable to find 'com/example/bookshelf/widgets/Widgets.gwt.xml' on your classpath; could be a typo, or maybe you forgot to include a classpath entry for source?
[ERROR] Failed to execute goal net.ltgt.gwt.maven:gwt-maven-plugin:1.3.0:codeserver (default-cli) on project bookshelf: Process exited with an error: 1 (Exit value: 1)
Take care with the groupId during the move: GWT’s coordinates changed from com.google.gwt to org.gwtproject at 2.10.0, so an old POM
that declares com.google.gwt:gwt-user:2.10.0 still resolves, but nothing newer is published there.
Gradle
GWT itself has no official Gradle plugin and no Gradle page in its documentation: the Developer Guide, the Getting Started page and the release notes mention Ant and Maven only. Gradle support is entirely community maintained. The state of the plugins on 2026-10-08, read from the Gradle Plugin Portal and the project repositories:
| Plugin | Version and activity | Notes from its README |
|---|---|---|
|
2.2.9, published 2026-01-31; repository last pushed 2026-06-16 |
A rewrite of the original plugin. Does not require you to declare |
|
1.3.1, published 2026-05-12; repository last pushed 2026-05-12 |
A fork of the unmaintained Putnami plugin adapted to GWT 2.13.0. Requires Java 17 and Gradle 8 or later; 1.3.0 added compatibility with Gradle 9. |
|
archived (last push 2017) |
Do not use. |
The minimal setup of the first plugin, as its README documents it:
plugins {
id "org.docstr.gwt" version "2.2.9"
}
gwt {
modules = ['com.example.bookshelf.App']
}
I did not run either Gradle plugin, so nothing about their task behaviour or compatibility with the Bookshelf is claimed beyond what
the READMEs say. If you choose Gradle, the equivalent of the multi-module layout above is a client project that applies the GWT plugin, a
shared project that exposes its sources to the client, and a server project; check how the plugin you pick makes the sources of
the shared project available to the compiler, since that is the part Maven solves with the sources classifier and gwt-lib. Maven
remains the build documented and exercised throughout this section, because gwt-maven-plugin, the archetypes and the GWT project’s
own samples are Maven.
The command-line creator tools
Before archetypes, the GWT SDK shipped command-line generators. They are history, and the official pages that still describe them have not kept up.
webAppCreator is a script in the SDK that runs com.google.gwt.user.tools.WebAppCreator from gwt-user. Its documented parameters
are -[no]overwriteFiles, -[no]ignoreExistingFiles, -templates (default sample,ant,eclipse,readme), -out, -junit, the
deprecated -[no]maven and -[no]ant, and the module name. The 2.11.0 release notes list the tool as deprecated. The class
itself prints this on every run (real output on 2.13.1):
java -cp gwt-user-2.13.1.jar:gwt-dev-2.13.1.jar com.google.gwt.user.tools.WebAppCreator \
-out foo -templates maven,sample,readme com.example.foo.Foo
WebAppCreator is deprecated - please refer to https://www.gwtproject.org/gettingstarted.html for other ways to create a new application with GWT, including with Maven archetypes. These encourage better separation of classpaths, and do not need IDE-specific setup.
Generating from templates: [maven, readme, _sample-test, sample]
Created directory foo
Created file foo/pom.xml
Created file foo/src/main/java/com/example/foo/Foo.gwt.xml
Created file foo/src/main/java/com/example/foo/client/Foo.java
Created file foo/src/main/java/com/example/foo/server/GreetingServiceImpl.java
Created file foo/src/main/webapp/WEB-INF/web.xml
What it generates is a single-module war project, the opposite of the layout recommended above. Its pom.xml (2.13.1) still pins
gwt-maven-plugin 1.2.0, runs the compile and test goals, sets maven.compiler.release to 11 and sourceLevel to 11, writes
the classes into WEB-INF/classes and disables Surefire in favour of gwt:test. It passes -compileReport and -XcompilerMetrics
in compilerArgs (see Compile Reports & Optimization); the latter is accepted by the compiler but is not listed in the compiler’s -help output. The official
Getting Started page (gettingstarted.html) still tells you to unpack gwt-2.10.0.zip, requires Java 1.8 and Apache Ant, and uses the
default Ant template with ant devmode and ant build, so it is stale; the same is true of the Compile & Debug chapter, which launches
DevMode through ant devmode. The deprecation, not those pages, is current. Use the archetype instead:
mvn archetype:generate \
-DarchetypeGroupId=net.ltgt.gwt.archetypes -DarchetypeArtifactId=modular-webapp -DarchetypeVersion=2026.6.22 \
-DgroupId=com.example.bookshelf -DartifactId=bookshelf -Dmodule-short-name=bookshelf
Source: tbroyer/gwt-maven-archetypes
i18nCreator (com.google.gwt.i18n.tools.I18NCreator) generates a properties file and a script that regenerates the Java interface (a
Constants, ConstantsWithLookup or Messages subinterface) when the properties change. It is not deprecated in the source and still
runs. Real output for a Messages interface:
$ java -cp gwt-user-2.13.1.jar:gwt-dev-2.13.1.jar com.google.gwt.i18n.tools.I18NCreator \
-createMessages com.example.foo.client.FooMessages
Created file src/com/example/foo/client/FooMessages.properties
Created file FooMessages-i18n
The generated script calls com.google.gwt.i18n.tools.I18NSync with the classpath of the machine where it was generated, which makes it
non-portable. The Internationalization page recommends writing the Messages interface first and the properties files from it,
which is the direction Maven builds prefer.
Tools the first book taught
GWT in Action by Hanson and Tacy (Manning, 2007) was written for GWT 1.x and shows a toolchain that no longer exists in GWT 2.13.1:
the projectCreator and applicationCreator scripts (an Eclipse-oriented pair run in that order), junitCreator, i18nCreator
and the shell and compile scripts generated beside the application, which started the hosted browser and the old com.google.gwt.dev.GWTCompiler.
In the source of 2.13.1:
| Tool in the book | Today |
|---|---|
|
|
|
|
|
Replaced by |
|
Removed in 2.0.0 RC2, with its function moved into the |
|
Still present. |
Ant |
Maven ( |
Use the book for the concepts of chapters 2 and 9 (the development cycle, the module concept, packaging a module as a JAR with sources), and this page for how those concepts are built today.
Pitfalls
| Symptom or message | Cause | Fix |
|---|---|---|
|
The plugin is declared without |
Add the extensions flag to the plugin declaration (in |
|
The module is not inherited, or the dependency has no sources JAR on the compiler classpath (for instance, running |
Declare both the binary and the |
|
|
Add |
|
|
Add |
|
A wildcard in |
Use |
|
A module descriptor was never generated (for instance |
Remove |
GWT compile is skipped and the application is empty |
|
Build the artifact with |
The compile uses a different Java level from |
|
Set |
|
Maven 3.9 skipping newer plugin betas that need Maven 4. |
Harmless noise; pin versions in |
GWT-RPC call fails with |
Server-side exception serialisation needs reflection into |
Start the servlet container JVM with |
Source: gwt-maven-plugin — gwt:test
Recent changes and where the books are outdated
-
2.10.0: the groupId becomes
org.gwtproject; the BOMorg.gwtproject:gwtcarries the managed versions. -
2.11.0: Jakarta artifacts (
gwt-servlet-jakarta,requestfactory-server-jakarta);webAppCreatordeprecated. -
2.12.0: minimum Java 11 for the tools; Java 12 to 17 language levels (hence
sourceLevel17 in the archetype); theApplicationCreatorandProjectCreatormain classes removed. -
2.13.0: the samples of the GWT repository moved to Maven; DevMode serves only static files by default; the notes say that 2.13 is probably the last release whose compiler and dev tools run on Java 11.
-
Plugin 1.3.0 (2026-05-31) and the archetype 2026.6.22 target 2.13.x; the archetype pins
jetty-ee10-maven-plugin12.1.10 for the server. -
Gradle: still no official plugin; two maintained community plugins, one archived.
-
The 2007 book (first edition) describes Ant,
projectCreator/applicationCreator, the hosted browser and theGWTCompilerclass: none of that exists. The second edition of the book (2013) has a Manning page that resolves but was not consulted for this page. The Google-hosted Getting Started and Compile & Debug pages also still describe the Ant flow of GWT 2.10.