What’s Changed & Migration
|
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), JDK 21 with -sourceLevel 17 and Jakarta Servlet 6.0 (the stack
the modular-webapp archetype 2026.6.22 generates), as of 2026-10-09; the release dates are those of the
official release notes. It is about everything that moved: the language and tooling jumps from GWT 1.x
to 2.x, what each 2.x release changed, how to migrate an existing project (build and server code: Maven coordinates, servlets, plugin; client
code compiled to JavaScript: JSNI, Elemental, CSS), and where older books, tutorials and even some official pages no longer match the toolkit. It is the
page to open when you inherit an application written for GWT 1.x to 2.8, when a tutorial or the 2007 first edition of GWT in Action tells you something that
does not compile, and when you have to decide the order of an upgrade. Each topic page of this section has its own "outdated" and "recent changes"
notes; this page collects them in one place.
Where to look first
| You have | Read |
|---|---|
A build that still uses |
com.google.gwt to org.gwtproject, the plugin recipe and Build Tools: Maven & Gradle |
A server on Tomcat 10+, Jetty 11+, Spring Boot 3+ or any Jakarta EE 9+ container |
|
Code with |
|
|
CSS to GSS and the old-to-current tables further down |
A book or tutorial that no longer works |
|
A statement of this section that contradicts an official page |
The 2.x line on one page
The long gaps are real: after 2.8.2 (2017-10-19) the project shipped 2.9.0 in 2020, 2.10.0 in 2022 and 2.11.0 in 2024, and 2.13.1 on 2026-06-19 is the newest release. The open milestone list of the GitHub repository and the roadmap name the direction: bring Java language features and the JRE emulation up to Java 21, and explore some J2CL-only JsInterop features in the GWT compiler. There is no GWT 3 release (J2CL & the Future).
From GWT 1.x to 2.x
The first edition of the book this section is compared with describes GWT 1.3 and 1.4 (2007) and Java 1.4 client code. Moving from that world to GWT 2 is less one upgrade than five: the language level, the event model, the development loop, the resources and the UI toolkit.
| Area | GWT 1.x | GWT 2.x (and 2.13.1) | Since |
|---|---|---|---|
Language |
Java 1.4 on the client: no generics, enums or annotations; RPC collections described with the |
Java 5 features arrived with 1.5 (generics, enums, annotations); 2.6 made Java 7 the default, 2.8 Java 8 syntax, 2.12 Java 12 to 17 syntax (records, text blocks, switch expressions, |
1.5, 2.6, 2.8, 2.12 |
Events |
Listener interfaces ( |
Handlers and |
1.6 (deprecation of the listeners) |
Images and resources |
|
|
2.0 |
Development loop |
Hosted mode: a special embedded browser started by |
2.0: "development mode" in your normal browser through a plugin, started by |
2.0, 2.5, 2.7, 2.8 |
Layout |
Table-based panels ( |
Layout panels ( |
2.0 |
UI construction |
Widgets assembled in Java code only |
UiBinder ( |
2.0, 2.1, 2.4, 2.1 |
Startup and size |
One monolithic script |
|
2.0, 2.5 |
Testing |
|
|
2.0 |
Distribution |
Platform-specific jars ( |
One platform-independent |
2.0, 2.10 |
Project layout |
|
The Maven multi-module layout generated by the |
2.10, 2.13 |
Three consequences of that table deserve a sentence each, because they are what breaks an old code base first.
-
The 2.0 notes introduce the terms "development mode" and "production mode" for what 1.x called hosted mode and web mode. Command lines changed with them:
-webbecame-prod,-portHostedbecame-codeServerPort, andjunitCreatordisappeared intowebAppCreator -junit.webAppCreatoris itself deprecated since 2.11.0 (see Getting Started). -
Deferred binding and the module XML survived the jump unchanged in spirit: a 1.x module descriptor with
<inherits>,<source>,<entry-point>,<servlet>still parses, but itsuser.agentassumptions do not (see the browser matrix below). -
Server code is Java, not GWT. A 1.x servlet that extends
RemoteServiceServletstill works if itsjavax.servletcontainer still exists; on a Jakarta container it needs the recipe below.
GWT 2.0 to 2.13.1: release highlights
| Release | Date | Highlights that affect application code or builds |
|---|---|---|
2.0.0 |
2009-12-09 |
In-browser development mode (the GWT Developer Plugin) replaces the hosted browser; |
2.1.0 |
2010-10-27 |
Cell widgets and an app framework for large applications (RequestFactory, Activities and Places); 2.1.1 (2010-12-17) adds a service layer to RequestFactory |
2.2.0 |
2011-02-11 |
HTML5 Canvas, Audio and Video, touch events, bidi support, sortable |
2.3.0 |
2011-05-02 |
IE9 support; HTML5 local storage; |
2.4.0 |
2011-09-07 |
|
2.5.0 |
2012-10-25 |
Super Dev Mode (experimental); Elemental (experimental); the ARIA library; fragment merging; source maps; |
2.6.0 |
2014-01-23 |
Java 7 is the default language level; the Super Dev Mode hook is on by default (RC4); the |
2.7.0 |
2014-11-20 (RC1 2014-10-30) |
Super Dev Mode is the default and DevMode starts it automatically; experimental GSS ( |
2.8.0 |
2016-10-20 (Beta1 2015-12-03) |
Java 8 syntax and library emulation; JsInterop graduates from experimental (the final specification); GSS is no longer experimental and legacy |
2.9.0 |
2020-05-13 |
Compiles with |
2.10.0 |
2022-06-09 |
Maven groupId changes to |
2.11.0 |
2024-01-09 |
Jakarta artifacts ( |
2.12.0 |
2024-10-29 |
Java 12 to 17 language features (text blocks, |
2.13.0 |
2026-02-11 |
DevMode server serves static files only by default; JFR events replace SpeedTracer; |
2.13.1 |
2026-06-19 |
Four bug fixes: files that cannot be deleted on Windows no longer fail the build, simpler iframe loading in Firefox, |
Source: GWT — Release notes
Behaviour changes that compile but behave differently
These are the changes that no compiler error will report. Two are in the 2.13.0 notes under "Breaking Changes"; the others are consequences of defaults that moved.
| Change | What it means for your code | Check |
|---|---|---|
|
Code that split on a delimiter and relied on the old emulated handling of empty leading or trailing entries can return a different array |
Search for |
|
The emulation matches the JVM (though not its Javadoc); a map built from nullable values fails at run time in the browser |
Search for |
|
A class that catches or throws it in shared code stops compiling for the client |
Keep it behind a server-only class or |
Source level (2.12.0 and 2.13.x) |
The command-line compiler’s |
The archetype sets |
|
Without it the compiler can hide JSNI and overlay errors behind "Ignored N units with compilation errors in first pass"; the archetype turns it on |
Make sure |
DevMode as application server (2.13.0) |
The embedded Jetty 9 launcher is no longer the default and is "due to be removed". A project that relied on |
Run the server (Jetty or Spring Boot) separately and use |
JPA/JDO enhanced classes over GWT-RPC (2.10.1 and 2.11.0) |
Entities that were enhanced by an ORM are no longer serializable by default; a warning explains it (the |
Send plain DTOs instead |
Flags and classes that are gone
Build scripts and run configurations written for 2.8 to 2.11 fail on options that no longer exist. The 2.12.0 notes list the removed, unused compiler flags; the 2.13.0 notes list removed classes.
| Release | Removed |
|---|---|
2.12.0 |
|
2.13.0 |
|
Browsers and the user.agent property
The user.agent values changed at every browser generation, and any deferred-binding rule or CSS condition that names an old value stops matching.
| Release | Values of user.agent |
|---|---|
1.7.0 |
|
2.6.0 (RC1) |
|
2.10.0 |
IE 8, 9 and 10 dropped. In 2.13.1 |
Source: GWT 2.13.1 — UserAgent.gwt.xml
Migration recipes
Migrate in this order; every step leaves the project buildable, and the order puts the build first because every later step needs the 2.13 tools.
com.google.gwt to org.gwtproject
GWT’s Maven coordinates changed in 2.10.0, and only the coordinates changed. The Java packages (com.google.gwt.user.client.ui, com.google.gwt.core.client), the module names
(com.google.gwt.user.User) and the *.gwt.xml files are exactly the same in 2.13.1, so no source file is touched by this step.
| Before (up to 2.10.0) | After (2.10.0 and later) | Notes |
|---|---|---|
|
|
import with |
|
|
versions 2.10.0 are published under both groupIds, 2.10.1 and later under |
|
|
2.11.0 and later; see the next recipe |
RequestFactory inside |
|
the groupId is |
|
none (see the Elemental recipe) |
last version 2.9.0; there is no |
|
same coordinates; the BOM pins 2.0.0, version 2.1.0 exists |
split out of GWT in 2.8.0 |
Source: org.gwtproject:gwt 2.13.1 — BOM
The repository metadata confirms the cut: com.google.gwt:gwt-user and gwt-servlet have 2.10.0 as latest and release version (last updated 2022-06-23), and asking for
a newer version under the old groupId fails like this:
Could not find artifact com.google.gwt:gwt-user:jar:2.13.1
The POM of that version does not exist in the old group on Maven Central (the request answers 404). The edit that fixes it is a groupId change plus a BOM:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.gwtproject</groupId>
<artifactId>gwt</artifactId>
<version>2.13.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.gwtproject</groupId>
<artifactId>gwt-user</artifactId> <!-- version from the BOM -->
</dependency>
<dependency>
<groupId>org.gwtproject</groupId>
<artifactId>gwt-dev</artifactId>
</dependency>
</dependencies>
If a library you depend on still declares com.google.gwt:gwt-user 2.10.0 transitively, import the new BOM first so Maven resolves a single version, and exclude the old coordinates.
javax to jakarta
A servlet container that implements Jakarta Servlet 5 or later (Tomcat 10+, Jetty 11+, Spring Boot 3+) has no javax.servlet classes, so GWT’s server classes that extend
javax.servlet.http.HttpServlet cannot load. GWT 2.11.0 added Jakarta variants; the client side is untouched.
| Stays the same | Moves |
|---|---|
|
|
Your DTOs and your service interfaces |
|
The module XML |
|
|
RequestFactory: |
|
the dependency |
The mismatch is easy to recognise because the compiler cannot even read the old servlet class against Jakarta Servlet 6. Compiling a servlet that still extends the javax base class
with gwt-user 2.13.1 and jakarta.servlet-api 6.0.0 on the class path printed (JDK 21):
src/com/example/bookshelf/server/LegacyCatalogServlet.java:3: error: cannot access HttpServlet
public class LegacyCatalogServlet extends RemoteServiceServlet {
^
class file for javax.servlet.http.HttpServlet not found
src/com/example/bookshelf/server/LegacyCatalogServlet.java:3: error: cannot access ServletException
public class LegacyCatalogServlet extends RemoteServiceServlet {
^
class file for javax.servlet.ServletException not found
2 errors
Changing the import to the jakarta package, with gwt-servlet-jakarta on the class path, compiled cleanly. The reverse mistake (a Jakarta servlet class deployed to a javax container)
fails when the container loads the class. Keep one flavour per deployment.
// Before: javax container
import com.google.gwt.user.server.rpc.XsrfProtectedServiceServlet;
// After: Jakarta container (Tomcat 10+, Jetty 11+, Spring Boot 3+)
import com.google.gwt.user.server.rpc.jakarta.XsrfProtectedServiceServlet;
Source: GWT — Release notes (2.11.0)
Two related facts move with this recipe. The validation annotations follow the same split: a javax.validation constraint on a DTO is silently ignored by a Jakarta Bean Validation
server and vice versa (see Validation). And on JDK 17 and later every reflectively serialized exception fails over GWT-RPC unless the container JVM is started with
--add-opens java.base/java.lang=ALL-UNNAMED or the class has a custom field serializer (upstream issue gwtproject/gwt#9793; see GWT-RPC).
The Codehaus plugin to the net.ltgt plugin
org.codehaus.mojo:gwt-maven-plugin (last release 2.10.0, 2022-06-25) is tied to one GWT version and one project layout. net.ltgt.gwt.maven:gwt-maven-plugin 1.3.0 is independent of the
GWT version, has the gwt-app and gwt-lib packagings, and is what the official Getting Started page and the archetype use. The detailed goal table and the migration order are in
Build Tools: Maven & Gradle; this is the shortest working summary.
| Codehaus (Mojo) plugin | net.ltgt plugin |
|---|---|
Compiles with its own GWT dependencies |
The project declares |
|
Only |
Dozens of |
Few properties plus list properties: |
One module holds the application |
Modules |
The descriptor is wherever it is |
|
Goal |
|
The command-line wildcard form -pl '*-client' that appears in several guides does not work with Maven 3.9: it fails with Could not find the selected project in the reactor: *-client.
Name the project (-pl bookshelf-client or -pl :bookshelf-client). Wildcards are a Maven 4 feature.
JSNI to JsInterop
JSNI (/*-{ ... }-*/ bodies of native methods and JavaScriptObject overlays) still compiles in 2.13.1, but JsInterop (stable since 2.8.0, compatible with J2CL since 2.9.0) replaces it
for new code and is the only style that carries over to J2CL. The mapping, verified by compiling both versions, is in JSNI & Overlay Types and
JsInterop; the short version:
| JSNI or overlay | JsInterop |
|---|---|
A |
A |
|
A native type |
|
|
|
|
Callbacks as JavaScript functions |
A |
Source: GWT — JsInterop
Rules that tripped the lab builds: the export flag -generateJsInteropExports is needed in compilerArgs and (for Super Dev Mode) in codeserverArgs;
the filters -includeJsInteropExports and -excludeJsInteropExports take Java regular expressions even though the 2.8.1 notes call them wildcards; of the newer annotations
only @JsIgnore and @JsOptional are acted on by the 2.13.1 compiler (@JsNullable, @JsNonNull, @JsAsync and @JsEnum compile but do nothing).
Elemental to Elemental2
The first Elemental library (announced as experimental in 2.5, generated from WebKit IDL) was deprecated in 2.9.0 and its source is not in the 2.13.1 tree; the last artifact is
com.google.gwt:gwt-elemental 2.9.0. Elemental2 (com.google.elemental2:elemental2-dom, latest stable 1.3.2; 1.4.0-RC1 is a pre-release; it is not managed by the GWT BOM, so declare its
version) is a set of JsInterop native types generated from the Closure externs and works in GWT 2 and J2CL.
| Elemental (1) | Elemental2 |
|---|---|
|
|
|
The constructors ( |
Elemental’s own collections and |
|
Vendor-prefixed WebKit APIs |
The standard names |
Source: Elemental2 — README
One Elemental2 behaviour matters during a port: Promise.all with a String type argument throws a ClassCastException when the promises resolve to strings; declare the element type Object.
See DOM & Elemental2 for the complete mapping.
CSS to GSS
Legacy CssResource syntax (@def, @eval, @sprite, @if user.agent) has been deprecated since 2.8.0, but it is still the default in 2.13.1: CssResource.enableGss is false,
CssResource.conversionMode is off and CssResource.gssDefaultInUiBinder is false. Automatic conversion is a bridge that the compiler says "will be removed in the next version of GWT".
The sequence that worked on the Bookshelf (details, converter output and every combination of the three properties are in Styling: CSS & GSS):
-
Turn on
CssResource.enableGssand setCssResource.conversionModetostrict, so remaining.cssfiles are converted in memory. -
Convert a folder with
com.google.gwt.resources.converter.Css2Gss(its class path needsgwt-devas well asgwt-user), review the.gssit wrote, fix what it dropped (comments, CSS hacks). -
Keep
@Source("book.css")as it is: withenableGsstrue the compiler prefers a.gssfile of the same base name. -
When no
.cssfile is referenced, delete them, removeconversionModeand setgssDefaultInUiBinderto true.
<!-- bookshelf App.gwt.xml: the bridge configuration -->
<set-configuration-property name="CssResource.enableGss" value="true" />
<set-configuration-property name="CssResource.conversionMode" value="strict" />
<!-- the end state: enableGss true, gssDefaultInUiBinder true, no conversionMode -->
Source: GWT — GSS Migration Guide
There is no GssResource type in 2.13.1: GSS is a syntax of CssResource selected by the property (the generator is GssResourceGenerator). DataGrid, CellTable and the other
widgets that embed legacy CSS need strict conversion or gssDefaultInUiBinder once enableGss is true.
Smaller replacements in code
Most of the rest of a 1.x to 2.x port is mechanical. Each snippet is the current form; the old form is named in the table in the next section.
// listeners to handlers
Button borrow = new Button(messages.borrow());
HandlerRegistration registration = borrow.addClickHandler(event -> presenter.borrow(book.getId()));
// later: registration.removeHandler();
// DeferredCommand to Scheduler
Scheduler.get().scheduleDeferred(() -> grid.setFocus(true));
// ServiceDefTarget.setServiceEntryPoint to an annotation on the interface
@RemoteServiceRelativePath("catalog")
public interface CatalogService extends XsrfProtectedService { /* ... */ }
Source: GWT — Delayed Logic
Outdated in older material, row by row
The table below keeps, in one place, every statement of the 2007 first edition (GWT 1.3 and 1.4) that is outdated, with the current form. The book’s concepts (RPC, widgets, deferred binding, JSNI) are still the right ideas; their APIs, tools and build steps are not. The same rows are repeated, with more detail, on the page named in the last column. The Developer Guide and tutorials of gwtproject.org are in most places accurate descriptions of the code, but some pages predate Java 8 and still show the listener era, JSNI or classic Dev Mode.
| Area | Older material | Current practice | Page |
|---|---|---|---|
Platform |
GWT 1.3/1.4, Java 1.4 client code (no generics, enums, annotations), JUnit 3 |
GWT 2.13.1, Java 17 source level ( |
|
Coordinates |
SDK zip with platform-specific |
Maven Central, |
|
Project creation |
|
The |
|
Dev loop |
Hosted mode ( |
Super Dev Mode ( |
|
Compiler |
|
|
|
Build |
Ant |
Maven ( |
|
Logging |
|
|
|
Browsers |
|
|
|
Bootstrap |
|
|
| Area | Older material | Current practice | Page |
|---|---|---|---|
Collections in RPC |
The |
Generics |
|
Events |
Listener interfaces ( |
Handlers ( |
|
Images |
|
|
|
Layout |
Table-based panels for page layout, |
Layout panels ( |
|
Deferred logic |
|
|
|
DOM |
Static |
|
|
JavaScript interop |
JSNI and |
JsInterop ( |
|
Generators |
Generators driven by Javadoc metadata |
Annotations; generators are discouraged because J2CL does not support them, annotation processors are preferred |
|
Styling |
Legacy |
GSS, opt-in with |
|
History |
A hidden history iframe |
Hash-change events; there is no history iframe in 2.13.1 |
| Area | Older material | Current practice | Page |
|---|---|---|---|
JSON |
The lenient |
|
|
RPC entry point |
|
|
|
Server push |
Blocking |
WebSockets or Server-Sent Events through JsInterop and Elemental2, or an asynchronous servlet |
|
External services |
Google AJAX Search, Google Video Search, Yahoo Search V1, Flash embedding |
All are gone; examples in this section use the scenario’s own APIs |
|
Concepts the book lacks |
Handlers, generics in RPC, |
Each has a page in this section; the index of this section lists them in reading order |
The second edition of the book (2013) has a Manning page that resolves, but it was not consulted for this section.
Errata and unsafe practices: what to do instead
Several older books, tutorials and sample projects (the first edition among them) contain practices that were never safe, and some code that never compiled. This section does not repeat them. It lists the topics and the correct current practice, with the page that explains it.
| Topic | Correct current practice | Page |
|---|---|---|
Credentials in examples and client code |
Use placeholders in every example. Keep credentials and keys on the server and out of client sources and of RPC payloads; send secrets only over TLS and verify every request on the server |
|
Markup built from user data or URLs |
Treat user data as text ( |
|
Opening URLs taken from remote data |
Validate scheme and host with |
|
Parsing third-party JSON |
Use strict parsing ( |
|
Proxy and upload servlets |
Authenticate and authorize the caller, bound the request size and type, choose the stored file name yourself, and never swallow exceptions |
|
Emulated server push |
Do not hold a container thread per client; use WebSockets, Server-Sent Events or an asynchronous servlet |
|
Cross-site request forgery on RPC and other state-changing endpoints |
Protect RPC with |
|
Content Security Policy |
Pick a linker and output style that fit the policy; measured combinations are on SafeHtml & XSS and XSRF, RPC & Server Security |
|
Code that does not compile and typos in API names |
Take class and method names only from the Javadoc of the version you build against; a Java keyword as a parameter name or a non-void method with an empty body is an error in any release |
|
GWT’s own security history |
Old XSS problems were in generated or bundled GWT code (2.4 and 2.5, the |
Source: GWT — Security
Corrections and deviations
While this section was being written every claim was checked against the 2.13.1 sources, the release notes and the live pages. Where a statement of the first plan, of an official page or of an earlier draft of this section differed from what the code does, the difference is recorded here so that you can trust the topic pages over the older text.
| Statement | What 2.13.1 shows |
|---|---|
"`-sourceLevel` defaults to the level of the running JDK" (the first reading of |
The command-line compiler’s default is 1.8 (the default argument registered by |
|
Confirmed: |
SDM became the default in "2.7" |
Confirmed as 2.7.0 RC1 (2014-10-30): "Super Dev Mode is now the default". 2.6.0 RC4 had turned the hook on by default |
The Developer Guide DevMode options ( |
They exist, but the 2.13.0 notes say the DevMode server serves static files only by default and the Jetty launcher is "due to be removed". The Developer Guide page does not say so |
|
Verified in the sources: |
|
The 2.12.0 notes say it has no effect; in 2.13.0 its argument handler was removed, so 2.13.1 rejects it as an unknown option and prints the usage. Use |
|
In-process permutation threads are capped by the system property |
|
Rejected by Maven 3.9.16 ( |
The gwt-maven-plugin usage page |
It still shows the BOM at 2.13.0 while the plugin is 1.3.0 |
Gradle |
There is no official Gradle plugin or documentation page; |
RequestFactory is in "maintenance mode" |
Not confirmed by any official source. The 2.13.1 BOM still publishes it (groupId |
| Statement | What was found |
|---|---|
|
Both answer 404; the JsInterop page is |
The JSNI restriction page |
It lives at |
The Gitter room, GWT Material and GIN URLs of the first plan |
GWT Material is |
Nalu is |
Current group id is |
RestyGWT 1.4 (2013) |
Central has releases up to 2.2.7 (2020); it needs |
GIN works with GWT 2.13 |
GIN 2.0.0 does not run on JDK 21 (Guice 3 fails with |
J2CL is "alpha" |
The J2CL README says both that the project is an alpha developer preview, not an official Google product, and that Google uses it in production. There is no GWT 3 release and the |
CVE-2012-5920 is fixed in 2.5.1 |
NVD lists 2.4 through 2.5 Final as affected; the 2.5.1 RC1 notes name only the |
| Statement | What was found |
|---|---|
|
|
The modules of the Developer Guide validation sample work on JDK 21 |
Client validation is deprecated since 2.8.0 and on JDK 21 needs |
The Developer Guide |
It does not (a missing |
Disabled log calls are removed from the compiled output |
In 2.13.1 only the handlers, popup and RPC proxy compile out; a disabled log call is not removed |
The compiler reports every error |
Without |
|
With the default obfuscated output the |
JsInterop filters are wildcards |
They are Java regular expressions in 2.13.1 (the 2.8.1 notes say wildcards) |
Deferred binding has |
No such element; use the |
RPC works the same on every JDK |
On Java 17 and later every reflectively serialized exception, |
A Spring Boot executable jar serves the module directory |
Not from |
Symbol maps ship in the war |
They are written under |
The |
|
Worked example: migrating the Bookshelf
The scenario of this section is a book catalogue. Suppose its first version was written for GWT 2.8 on Tomcat 8: the Codehaus plugin, com.google.gwt coordinates, a javax servlet and one
JSNI method to format a number with the browser’s Intl. This is the migration, step by step. The "after" files are the files of the scratch application that passed mvn verify (nine tests)
on GWT 2.13.1 and JDK 21.
Step 1: POM and plugin
<!-- before -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>2.8.2</version>
</dependency>
<!-- after: parent POM -->
<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>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.3.0</version>
<extensions>true</extensions>
<configuration>
<sourceLevel>17</sourceLevel>
<failOnError>true</failOnError>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
The client module uses <packaging>gwt-app</packaging>, declares gwt-user and gwt-dev without versions, and passes -generateJsInteropExports in compilerArgs.
Source: gwt-maven-plugin — Basic usage
Step 2: the servlet
package com.example.bookshelf.server;
import com.example.bookshelf.shared.Book;
import com.example.bookshelf.shared.BookNotFoundException;
import com.example.bookshelf.shared.BookPage;
import com.example.bookshelf.shared.CatalogService;
import com.google.gwt.user.server.rpc.jakarta.XsrfProtectedServiceServlet;
public class CatalogServiceImpl extends XsrfProtectedServiceServlet implements CatalogService {
@Override
public BookPage getBooks(int start, int length) {
return CatalogRepository.INSTANCE.page(start, Math.min(length, 100));
}
@Override
public Book saveBook(Book book) throws BookNotFoundException {
if (book.getTitle() == null || book.getTitle().isBlank() || book.getTitle().length() > 200) {
throw new IllegalArgumentException("Invalid title");
}
return CatalogRepository.INSTANCE.save(book);
}
// getBook(long id) omitted
}
The import is the only difference from the javax version; the server also validates its input, because nothing the browser sends can be trusted. web.xml moves to the
https://jakarta.ee/xml/ns/jakartaee namespace (version 5.0), declares gwt.xsrf.session_cookie_name and maps XsrfTokenServiceServlet (the jakarta class) next to the service servlet.
Source: GWT — XSRF protection
Step 3: the JSNI method
// before: JSNI in a widget, legacy but still compiles in 2.13.1
// private static native String format(String locale, double n) /*-{ ... }-*/;
// ("..." is the native body that called the browser's Intl.NumberFormat)
// after: a native JsInterop type, no JavaScript text
package com.example.bookshelf.client.interop;
import jsinterop.annotations.JsType;
@JsType(isNative = true, namespace = "Intl", name = "NumberFormat")
public class NumberFormatJs {
public NumberFormatJs(String locale) {}
public native String format(double value);
}
Used as new NumberFormatJs("es").format(1234.5). Exporting the same logic to the host page is one more annotation, and needs the compiler flag:
@JsType(namespace = "bookshelf", name = "Api")
public final class ExportedApi {
private ExportedApi() {}
@JsMethod
public static String formatCopies(String locale, double n) { return new NumberFormatJs(locale).format(n); }
}
In the browser bookshelf.Api.formatCopies('es', 1234.5) returned "1234,5" (no grouping separator for four digits with the es locale). Without -generateJsInteropExports in both compilerArgs
and codeserverArgs the call fails with ReferenceError: bookshelf is not defined.
Source: GWT — JsInterop
Step 4: module XML and styling
<module>
<inherits name="com.google.gwt.user.User" />
<inherits name="com.google.gwt.user.theme.clean.Clean" />
<inherits name="com.google.gwt.i18n.I18N" />
<inherits name="com.google.gwt.place.Place" />
<inherits name="com.google.gwt.activity.Activity" />
<inherits name="com.google.gwt.editor.Editor" />
<source path="client" />
<source path="shared" />
<entry-point class="com.example.bookshelf.client.App" />
<extend-property name="locale" values="en" />
<extend-property name="locale" values="es" />
<set-configuration-property name="CssResource.enableGss" value="true" />
<set-configuration-property name="CssResource.conversionMode" value="strict" />
</module>
The module names are unchanged from GWT 2.8, which is why this step usually needs no edit other than the two GSS properties. The module file moves to src/main/module.gwt.xml
(or the plugin is told skipModule).
Source: GWT — Organize Projects
Step 5: run and check
mvn -B verify # build everything, run the 9 tests
mvn -B gwt:codeserver -pl bookshelf-client -am # Super Dev Mode on http://127.0.0.1:9876/
The code server printed its ready line (ready at http://127.0.0.1:9876/) with the embedded Jetty 9.4.58; -pl '*-client' would have failed on Maven 3.9, as noted above. Run the server module
separately, for example with jetty-ee10-maven-plugin 12.1.10 and -Djetty.http.port=8081.
Pitfalls
The messages below were printed during the Bookshelf work on 2.13.1 with JDK 21 (a few from JDK 26 with the same build).
| Symptom (as printed) | Cause | Fix |
|---|---|---|
|
2.13.1 is not published under the old groupId; |
Change the groupId to |
|
A servlet extends the |
Import |
|
A wildcard |
|
|
|
Keep the descriptor at |
|
The |
Run |
|
A dashed class name has no accessor; the accessor |
Add an accessor, or declare the name with |
|
|
|
|
A |
Convert every file of the list, or none |
|
|
Set |
|
A server-side exception serialized by GWT-RPC on Java 17 or later (gwtproject/gwt#9793) |
Start the container JVM with |
|
An XSRF-protected call without the token (this reply appears once the |
Fetch the token from |
|
Client-side validation on JDK 21 without |
Pass |
|
Messages with plurals but no locale file for |
Add an (even empty) |
|
Obfuscated default output with a missing permutation |
Check that the permutation file was deployed next to the |
|
A UiBinder |
Declare |
A blank screen and no error |
A |
Use |
Source: GWT — Compile & Debug
A migration checklist
-
Pin the toolchain: JDK 21 (the tools need Java 11 or later), Maven 3.9, GWT 2.13.1,
gwt-maven-plugin1.3.0. -
Replace the plugin and the groupId, import the
org.gwtproject:gwtBOM and delete explicit GWT versions. -
Set
sourceLevelto 17 andfailOnErrorto true, and fix the new errors one at a time. -
Switch servlets to
jakartaif the container requires it, and set--add-opens java.base/java.lang=ALL-UNNAMEDfor exception serialization on Java 17 and later. -
Run Super Dev Mode with
gwt:codeserverfrom the root POM; run the server separately. -
Replace listener interfaces,
ImageBundle,DeferredCommandandDOM.*calls with their current forms; the compiler’s deprecation warnings list them. -
Convert CSS to GSS folder by folder with
strictconversion as the bridge. -
Convert JSNI to JsInterop, Elemental to Elemental2; add the export flag to both
compilerArgsandcodeserverArgs. -
Review
String.split,Collectors.toMapandAccessControlExceptionuses for the 2.13.0 changes. -
Review security: user data as text,
SafeHtmlfor markup, XSRF protection, a CSP that matches the chosen linker and output style. -
Run
mvn verifyand a manual Super Dev Mode session before moving on to J2CL-oriented clean-ups.
Related pages
The topic pages quoted above give the detail: Build Tools: Maven & Gradle, Compiler & Compilation, JsInterop, Styling: CSS & GSS, SafeHtml & XSS and XSRF, RPC & Server Security. The landing page of this section lists all pages in reading order and holds the bibliography, and the cheat sheet condenses the flags and coordinates into a one-page PDF.