Development Mode & Debugging

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, gwt-maven-plugin 1.3.0 (net.ltgt.gwt.maven), a JDK 21 toolchain with -sourceLevel 17 and Jakarta Servlet 6 on the server side (Jetty 12 EE 10), and was checked on 2026-10-08; the code server and DevMode runs shown here were executed on that stack with the Bookshelf scratch application. It is about build and tooling code, the compiler process you keep running while you write client code, and how it cooperates with your server code. The application sources themselves are unchanged by anything below, except for the optional GWT.log and GWT.debugger calls in the debugging section.

The development loop at a glance

A GWT application is Java that is compiled to JavaScript, so there are two questions during development: how do you see a change without a full production compile, and how do you debug code that the browser runs as JavaScript. Since GWT 2.7 the answer to both is Super Dev Mode (SDM):

  • a separate Java process, the code server, compiles your modules on demand in draft mode and serves the result;

  • the browser loads that output instead of the deployed output, and you debug it in the browser devtools through source maps that point back to your .java files;

  • your own server (Jetty, Tomcat, Spring Boot) serves the host page and the RPC or REST endpoints exactly as in production.

The same pieces exist under several names, and the official documentation still mixes them:

Table 1. Development tools and their status in GWT 2.13.1
Tool What it is Status

CodeServer (com.google.gwt.dev.codeserver.CodeServer, artifact gwt-codeserver)

The Super Dev Mode compiler and web server, a plain Java program. mvn gwt:codeserver runs it.

The recommended tool. This is what the rest of the page is about.

DevMode (com.google.gwt.dev.DevMode)

A Swing window plus an optional embedded web server that starts the code server for you (-superDevMode defaults to on).

Still shipped. Since 2.13.0 its embedded server only serves static files, and using it as an application server is deprecated.

Classic Dev Mode (browser plugin, -nosuperDevMode)

Ran the Java bytecode in the JVM and drove the browser through a plugin.

Deprecated in 2.8.0, history only (see below).

Hosted mode (GWTShell)

The name of classic Dev Mode in the early GWT versions.

Gone. The official FAQ still carries a note that "Hosted mode" and "web mode" were renamed development mode and production mode.

How Super Dev Mode works

The code server is an ordinary Java program with an embedded Jetty 9.4 web server (the GWT 2.13.1 BOM manages 9.4.58). It does not run your client code in the JVM: it compiles the whole module to JavaScript, as the production compiler does, but in draft mode with most optimizations off. That is why the core guarantee of old Dev Mode, "the Java ran the same as the JavaScript", is replaced by a simpler one: what you debug is the compiled JavaScript, shown to you as Java.

sequenceDiagram participant B as Browser participant H as Host server (Jetty on 8081) participant C as CodeServer (127.0.0.1:9876) participant J as Compiler (draft mode, incremental) B->>H: GET /bookshelf/bookshelf.nocache.js H-->>B: stub script written into launcherDir B->>C: GET /recompile-requester/bookshelf C-->>B: recompile script B->>B: confirm that the code server is ready B->>C: GET /recompile/bookshelf with the binding properties C->>J: compile only what changed since the last compile J-->>C: new output directory (compile-N) C-->>B: JSON status ok, or failed with the old output kept B->>C: GET bookshelf.nocache.js and the .cache.js files B->>C: GET the source map and the .java files on demand

Reading the diagram from the top: the page asks the host server for the module’s bookshelf.nocache.js. The -launcherDir output directory holds a small stub in that place, so the request reaches the code server, which recompiles if any source changed and answers with the real bootstrap script. Without a -launcherDir you do the same by hand with the bookmarklets (next section). When a compile fails, the code server logs the errors and continues to serve the previous version.

The article states the same thing from the other side: Super Dev Mode "replaces the internals of Dev Mode" and, because it needs no plugin, works in modern browsers and on mobile browsers. It requires the xsiframe linker, which has been the default since GWT 2.7, so a current module needs no configuration for it.

Starting the code server

With Maven

With gwt-maven-plugin the goal is gwt:codeserver. It is an aggregator goal: according to the plugin documentation, its configuration lives in the root POM, not in the gwt-app module, and it runs the code server for the gwt-app modules of the reactor. The modular-webapp archetype used by the Bookshelf scratch application generates this configuration in the root POM:

<plugin>
  <groupId>net.ltgt.gwt.maven</groupId>
  <artifactId>gwt-maven-plugin</artifactId>
  <inherited>false</inherited>
  <configuration>
    <launcherDir>${basedir}/bookshelf-server/target/gwt/launcherDir</launcherDir>
    <!-- To be able to use gwt:devmode (useful on Windows where Ctrl+C won't kill gwt:codeserver)-->
    <warDir>${basedir}/bookshelf-server/target/gwt/launcherDir</warDir>
    <devmodeArgs>
      <arg>-noserver</arg>
    </devmodeArgs>
  </configuration>
</plugin>

Three details are worth knowing. The goal puts the sources of the reactor modules that you depend on as gwt-lib or with the sources classifier on the code server’s source path as they are, so an edit in bookshelf-shared is picked up without rebuilding it. Dependencies use the runtime scope by default (classpathScope), where gwt:compile uses compile. And because the code server takes different arguments from the compiler, an option that both need, such as a custom -setProperty, has to be given twice, in compilerArgs for the production compile and in codeserverArgs for the code server.

The plugin documentation recommends limiting the reactor to client modules so that the server is not built on every start:

mvn gwt:codeserver -pl bookshelf-client -am

The real output of this command on the scratch application (JDK 21, GWT 2.13.1, plugin 1.3.0; the middle of the Maven log is omitted):

[INFO] --- gwt:1.3.0:codeserver (default-cli) @ bookshelf ---
[INFO] Turning off precompile in incremental mode.
[INFO] Super Dev Mode starting up
[INFO]    workDir: .../app/target/gwt/codeserver
[INFO]    Loading Java files in com.example.bookshelf.App.
[INFO]    Module setup completed in 1651 ms
  [WARNING] ...:INFO:oejs.Server:main: jetty-9.4.58.v20250814; ...
  [WARNING] ...:INFO:oejs.AbstractConnector:main: Started ServerConnector@...{HTTP/1.1, (http/1.1)}{127.0.0.1:9876}
[INFO] The code server is ready at http://127.0.0.1:9876/

The [WARNING] prefix on the Jetty lines is only the Maven plugin forwarding the forked process’s standard error; they are informational. The code server turns off -precompile itself ("Turning off precompile in incremental mode", printed by its Options class) because an incremental compile works on one permutation, as explained below.

The same goal has launcherDir, logLevel, style, sourceLevel, failOnError, modules, projects, jvmArgs and codeserverArgs parameters. On the scratch application, codeserverArgs containing -port 9886 was placed in the client module’s POM and had no effect (the server still started on 9876) because the aggregator goal reads its configuration from the root POM, so put any port change there.

Without Maven

Run com.google.gwt.dev.codeserver.CodeServer from gwt-codeserver with gwt-dev and the application classpath, giving at least one module name. This is the 2.13.1 argument list as printed by the class itself:

CodeServer [-[no]allowMissingSrc] [-[no]compileTest] [-compileTestRecompiles count] [-[no]failOnError]
  [-[no]precompile] [-port port] [-src dir] [-workDir dir] [-launcherDir]
  [-bindAddress host-name-or-address] [-style (DETAILED|OBFUSCATED|PRETTY)]
  [-setProperty name=value,value...] [-[no]incremental]
  [-sourceLevel [auto, 1.8, 9, 10, 11, 17]]
  [-logLevel (ERROR|WARN|INFO|TRACE|DEBUG|SPAM|ALL)] [-[no]generateJsInteropExports]
  [-includeJsInteropExports/excludeJsInteropExports regex]
  [-XmethodNameDisplayMode (NONE|ONLY_METHOD_NAME|ABBREVIATED|FULL)]
  [-X[no]closureFormattedOutput] [module]

Run with no module, the program prints the line Usage: at least one module must be supplied. The flags that matter every day:

Table 2. CodeServer flags (GWT 2.13.1)
Flag Purpose Default

-port

The TCP port of the code server.

9876 through the Maven goal and DevMode

-src

A directory of GWT source prepended to the class path used for compiling. Repeatable. Under Maven the plugin passes one per source root.

none

-launcherDir

The directory into which the files for launching Super Dev Mode, including the stub *.nocache.js, are written.

none (use the bookmarklets)

-workDir

The root of the tree where the code server writes compiler output (compile-1, compile-2 and so on).

a temporary directory

-bindAddress

The address for the code server and its web server.

127.0.0.1

-[no]incremental

Reuses data from the previous compile.

on

-[no]precompile

Compiles the modules once when the server starts.

on, switched off by the code server when incremental

-style

Script style: DETAILED, OBFUSCATED or PRETTY. PRETTY gives readable names in stack traces.

OBFUSCATED

-setProperty

Fixes a deferred-binding property, for example user.agent=safari, to reduce the permutations.

none

-[no]failOnError

Stop if any input file has an error, even if unused.

off

-sourceLevel

The Java source level. The help text and the Developer Guide are right: the default is 1.8 (a default argument registered in ToolBase). Java 12-17 syntax needs an explicit -sourceLevel 17, and auto resolves to the level of the running JVM.

1.8

-[no]allowMissingSrc

Allows -src to name directories that do not exist yet.

off

-[no]compileTest, -compileTestRecompiles

Compile (and recompile) once and exit with status 0 on success. Useful in a continuous-integration check that the code server can build the module.

off

Triggering a recompile: bookmarklets or launcherDir

There are two ways to tell the browser "use the code server’s build of this module". Both end up in the same state: the original *.nocache.js of the page finds a redirect and loads the code server’s output.

Bookmarklets

When you start the code server without -launcherDir, you compile and deploy the application once, serve its host page from your own server, and then open the code server page (http://127.0.0.1:9876/). It lists two bookmarklets, Dev Mode On and Dev Mode Off, to drag onto the bookmarks bar. On your application page click Dev Mode On, pick the module and press Compile: the code server compiles the module, stores its URL in the page’s session storage and reloads the page. The page’s own bookshelf.nocache.js finds the entry and redirects to the code server’s copy:

window.sessionStorage["__gwtDevModeHook:bookshelf"]
// returns "http://127.0.0.1:9876/bookshelf/bookshelf.nocache.js"

The key is stored in session storage, so it is per browser tab and disappears when the tab is closed. Dev Mode Off removes it and the page returns to the deployed output. The compile button in the dialog is itself a bookmarklet that can be dragged to the toolbar.

The launcherDir approach

With -launcherDir the code server writes a stub bookshelf.nocache.js into that directory. If you make the directory part of your web application (the archetype’s env-dev profile adds it as an extra Jetty resource base, and gwt:codeserver and gwt:devmode both write to it), the host page’s script tag loads the stub instead of the real bootstrap. The stub inserts the code server’s recompile script at the top of the page head, and that script asks the code server to recompile on every page reload. Its source is short; the relevant part of the file written on the scratch application reads:

var hostName = $wnd.location.hostname;
var serverUrl = 'http://' + hostName + ':9876';
var module = 'bookshelf';
var nocacheUrl = serverUrl + '/recompile-requester/' + module;

and, if the script cannot be loaded, it shows a confirm dialog:

Couldn't load bookshelf from Super Dev Mode
server at http://localhost:9876.
Please make sure this server is ready.
Do you want to try again?

The two approaches compare as follows:

Table 3. Bookmarklets versus launcherDir
Aspect Bookmarklets launcherDir stub

Setup

Drag two bookmarklets once per browser.

One path in the build; nothing in the browser.

Reload behaviour

After Dev Mode On the page recompiles when you press Compile in the dialog or the bookmarklet.

Every page reload asks the code server to recompile and waits for the result.

Host page needs

A deployed build of the application (its *.nocache.js does the redirect).

Nothing deployed: the stub is the module’s bootstrap in development.

Works for

Any server, including one you do not control.

A server that can serve the launcher directory as static resources.

Danger

None specific.

The stub must never reach production (see the security section).

Incremental compiles

Draft-mode compiling alone would still take seconds per change. SDM adds incremental compilation (-incremental, on by default): the code server keeps a rebuild cache of the compiled types and, on the next compile, only re-traverses types that depend on a changed source file. The release notes add that it also caches this data on disk, which speeds up the first compile after a restart.

Incremental compilation compiles one permutation per request. The bookmarklet or the stub sends the browser’s actual binding properties (the user.agent and locale that deferred binding would choose), so the code server compiles exactly the permutation your browser will run. The first request in the scratch application, issued by hand without those properties, shows what happens when the properties expand to more than one permutation:

[INFO] GET /recompile/bookshelf
[INFO]    Job com.example.bookshelf.App_1_0
[INFO]       starting job: com.example.bookshelf.App_1_0
[INFO]       binding: user.agent=safari
[INFO]       Current binding properties are expanding to more than one permutation but incremental compilation requires that each compile operate on only one permutation.
[INFO]       0,022s total -- Compile completed

With both properties given (locale and user.agent) the first compile of the Bookshelf, which has three locales and two user agents, succeeded and the next edit of one string was recompiled much faster:

[INFO] GET /recompile/bookshelf
[INFO]       binding: locale=en
[INFO]       binding: user.agent=safari
[INFO]          Unification traversed 26992 fields and methods and 2568 types. 2527 are considered part of the current module ...
[INFO]             Linking per-type JS with 2506 new/changed types.
[INFO]             Source Maps Enabled
[INFO]          Compilation succeeded -- 9,149s
[INFO]       9,375s total -- Compile completed
(after changing one @DefaultMessage string)
[INFO]       binding: locale=en
[INFO]       binding: user.agent=safari
[INFO]          Unification traversed 1103 fields and methods and 844 types. 8 are considered part of the current module ...
[INFO]             Linking per-type JS with 8 new/changed types.
[INFO]          Compilation succeeded -- 0,485s
[INFO]       0,563s total -- Compile completed

The first compile took 9.1 s, the incremental one 0.56 s with 8 changed types instead of 2506. A request with no change at all is answered without compiling (skipped compile because no input files have changed, 13 ms). A restart of the code server is faster than the first start because of the on-disk cache (3.9 s for the same module in the last run). Times are from a laptop and only meant as orders of magnitude.

To shorten the cold compile, narrow the permutations for development with -setProperty, or in a development module with set-property (for example user.agent fixed to the browser you use). The module-XML side of that is covered on Compiler & Compilation.

Debugging in the browser with source maps

Because SDM runs JavaScript, you debug it in the browser’s devtools. The compiler emits a source map (Source Maps Enabled appears in the compile log; since 2.12.0 source maps are on in all browsers), and the compiled .cache.js ends with a sourceMappingURL comment that points to the code server:

//# sourceMappingURL=http://127.0.0.1:9876/sourcemaps/bookshelf/DF97226FAEF9B2B2AA29587BF7177179_sourcemap.json

The devtools then request the Java files themselves from the code server, so the Sources panel shows com/example/bookshelf/client/... files you can set breakpoints in. The article describes the URL shape; on the scratch application:

curl -s http://127.0.0.1:9876/sourcemaps/bookshelf/com/example/bookshelf/client/App.java

returned the file after the first compile (the status code is 200 with Content-Type: text/plain) and a 404 with the page message This module hasn't been compiled yet. before it. This is also the first sign of the security caveat below: the source of the module is served to whoever asks.

Plan your debugging session around what SDM is and is not:

  • Breakpoints and stepping in the Java files work. The article recommends Chrome, where support is best, and notes that you may have to enable source maps in the devtools settings.

  • Variables are JavaScript values: the field names and values you see in the scope panel are those of the compiled code, not Java ones.

  • No JVM checks. SDM does not run the application in a JVM, so some runtime checks do not happen; the article gives the array index out of bounds as an example. The production compile can also turn off or reduce checks that SDM’s draft build still performs, so run a production build before release.

  • Logging. GWT.log writes to the browser console in SDM (and is removed from production output). GWT.debugger() emits a JavaScript debugger statement that stops when the devtools are open, with no effect in server code. The Javadoc for the shared GWT class documents both; java.util.logging can be routed to the console as well (see Logging).

The following change to the Bookshelf presenter uses com.google.gwt.core.shared.GWT rather than the client class, so that the presenter stays unit-testable in a plain JVM; the module compiled under the code server with the two calls, and the string Loading books is present in the compiled output.

import com.google.gwt.core.shared.GWT;

public void load(int start, int length) {
  GWT.log("Loading books " + start + ".." + (start + length));
  service.getBooks(start, length, new AsyncCallback<BookPage>() {
    @Override
    public void onSuccess(BookPage page) {
      display.showRows(start, page.getBooks(), page.getTotal());
    }

    @Override
    public void onFailure(Throwable caught) {
      GWT.log("getBooks failed", caught);
      GWT.debugger(); // stops here when the browser devtools are open
      display.showError(caught.getMessage());
    }
  });
}

Never log secrets, session tokens or whole user records with GWT.log or System.out even in development: console output reaches screenshots, bug reports and browser extensions. Remove any GWT.debugger() before merging; it is harmless to users with closed devtools but is a leftover that makes a production page stop for anyone who opens them.

Debugging from an IDE and the server side

No official page documents an IDE-specific procedure for SDM, so only the general shape is given here.

  • Client code. An IDE that can attach to the browser’s JavaScript debugger and honours source maps can set breakpoints in .java files in the same way the devtools do, because the maps and the sources both come from the code server URL. This is a feature of the IDE, not of GWT, and works only while the code server is running.

  • Shared and server code. Code in the shared module that is also compiled for the JVM, and everything in the server module, runs in an ordinary JVM, so a normal Java debugger attached to the servlet container (or to mvn jetty:run) works as always. Attach with the JVM’s standard -agentlib:jdwp option in MAVEN_OPTS, or use your IDE’s Maven or Spring Boot run configuration with Debug.

  • Presenters and plain Java. The best IDE debugging experience for client logic remains a JUnit test of a presenter whose view and service are fakes (Testing covers it); no code server or browser is needed.

  • The code server’s own JVM. To debug a generator or an annotation processor that runs inside the compiler, start the code server (or the Maven goal’s forked JVM, jvmArgs) with a JDWP agent. This is rarely needed.

The tutorial in the official documentation debugs StockWatcher in an IDE with classic Dev Mode ("Debug As" in Eclipse). That procedure no longer applies to current GWT, and the Eclipse plugin text it relies on is historical.

DevMode today

com.google.gwt.dev.DevMode is still shipped and still the main class of the old ant devmode and Eclipse launch configurations. In 2.13.1 it is a thin shell around SDM, and its role changed in 2.13.0:

  • -superDevMode is on by default (ArgHandlerSuperDevMode.getDefaultValue() returns true). Setting it to -nosuperDevMode is the deprecated classic mode.

  • DevMode starts the code server by reflection (SuperDevListener); if gwt-codeserver is not on the class path the log says: Unable to find main() method for Super Dev Mode code server. Hint: verify that gwt-codeserver.jar is in your classpath.

  • -[no]startServer (default on) starts an embedded servlet container for the directory given with -war. -noserver is a documented alias of -nostartServer in DevModeBase. With it you run your own server and DevMode only compiles and starts the code server.

  • -startupUrl url adds a URL that DevMode offers to open ("Automatically launches the specified URL"); a URL without a host is taken as localhost and the embedded server’s port.

  • -port is the embedded web server’s port (8888), -codeServerPort the code server’s (9876 in SDM, 9997 for classic). -bindAddress defaults to 127.0.0.1.

  • -war, -deploy, -extra, -workDir, -style, -setProperty, -incremental and -sourceLevel work as for the compiler and the code server.

DevMode opens a Swing window. Started with -Djava.awt.headless=true on the scratch application with -noserver, it fails before doing anything else:

java.lang.RuntimeException: Error running on Swing UI thread
	at com.google.gwt.dev.SwingUI.invokeAndWait(SwingUI.java:411)
	...
Caused by: java.awt.HeadlessException

That makes DevMode unsuitable for servers, containers and CI. mvn gwt:codeserver, which has no GUI, is the replacement, and the plugin keeps gwt:devmode only for standalone applications that have no server of their own (client code only, or CORS to an API). For those, point startupUrls at a host page from the module’s public path.

The 2.13 static-file-only server

Up to 2.12, -startServer launched an embedded Jetty that also ran your servlets from WEB-INF (so you could serve RPC from DevMode). The 2.13.0 release notes changed the default:

DevMode server defaults to only serving static files.
— GWT 2.13.0 release notes
https://www.gwtproject.org/release-notes.html#Release_Notes_2_13_0

The default server class is now StaticResourceServer: a Jetty ResourceHandler that serves the -war directory and nothing else (no servlets, no web.xml). The previous launcher is still present, selected with -server com.google.gwt.dev.shell.jetty.JettyLauncher, but the release notes describe it as "due to be removed", and the 2.11 notes had already deprecated "using DevMode as an application server". The official advice is to split the client and server class paths, or to implement a ServletContainerLauncher that runs another server (the notes link a sample project that does this), or simply to run your servlet container separately.

# DevMode with the legacy launcher (not recommended, due to be removed)
java -cp "$CP" com.google.gwt.dev.DevMode \
  -server com.google.gwt.dev.shell.jetty.JettyLauncher \
  -war target/gwt/launcherDir \
  -startupUrl index.html \
  com.example.bookshelf.App

The -server flag takes servletContainerLauncher[:args], either a fully qualified class or the name of a ServletContainerLauncher registered with the Java ServiceLoader. Specifying -server overrides -noserver.

For a real servlet container or Spring Boot application, which is what the Bookshelf uses, none of this is needed: run the server as you do in production (here the Maven Jetty plugin with -Denv=dev so the launcher directory is served) and run gwt:codeserver beside it.

# terminal 1: the client side, recompiles on demand
mvn gwt:codeserver -pl bookshelf-client -am

# terminal 2: the server side (Jetty 12 EE 10 with the launcher directory as an extra resource base)
mvn -pl bookshelf-server -Denv=dev org.eclipse.jetty.ee10:jetty-ee10-maven-plugin:12.1.10:run -Djetty.http.port=8081

Open http://localhost:8081/ and the browser asks the code server (on 9876) for the module. A Spring Boot server works the same way as long as it serves the launcher directory as a static resource location in its development profile; the GWT side does not care which server delivers the host page. The same arrangement is the answer to the DG section on EJBs: GWT provides the code server or the -noserver option for that case.

Classic Dev Mode and the plugins: history only

Before GWT 2.7 (and still in the 2.x documentation text), development mode meant the JVM executing the Java bytecode of your client code, connected to a regular browser through a browser plugin (the GWT Developer Plugin), with the Java debugger of the IDE attached to that JVM. It gave full Java debugging, and the DevMode window showed a log tree with GWT.log messages, a Restart Server button for the embedded Jetty, and the -logLevel SPAM option for the engine’s internal messages. The approach needed a plugin per browser. The 2.8.0 release notes list classic Dev Mode under deprecations (with a temporary switch back, gwt.args='-devMode', for test cases), and since 2.7 launching DevMode starts Super Dev Mode. A modern application cannot use the plugin; its remnants are the flags -nosuperDevMode and -codeServerPort 9997 and the screenshots of the Swing window in the older text.

The first edition of the Manning book GWT in Action (Hanson and Tacy, 2007; it targets GWT 1.3 and 1.4) describes the earlier hosted mode, a launcher class GWTShell with an embedded browser and Tomcat, for debugging in an IDE; that is the ancestor of the mechanism above and none of its procedures, class names or screenshots apply to GWT 2.13. Everything it says about breakpoints in client Java belongs to the pre-Super-Dev-Mode era.

Security: the code server must never be exposed

The Super Dev Mode article is explicit that the code server has no authentication and hands all of your Java source code to anyone who asks, and therefore recommends running it only on localhost or behind a firewall. In the code (WebServer.java) every static response carries Access-Control-Allow-Origin: *, which was verified on the scratch application:

curl -s -D - -o /dev/null http://127.0.0.1:9876/bookshelf/bookshelf.nocache.js | grep -i access-control
# Access-Control-Allow-Origin: *

Treat the port as a development-machine-only interface:

  • Keep the default -bindAddress 127.0.0.1. Do not bind to 0.0.0.0 or forward the port from a shared host, VM or container to a network you do not control: anyone who can reach it can read your sources through /sourcemaps/... and trigger compiles (including /clean, which wipes the disk cache). For testing from a phone, use a trusted network and shut the code server down afterwards.

  • The article says that https is not supported (a referenced upstream issue is gwtproject/gwt#7535), so a host page served over https loads a script from an http origin, which browsers treat as mixed content and may block. Test with http hosts or use a browser setting for local development.

  • Never deploy the launcher directory or its stub *.nocache.js. The stub names a code server on the page’s host and port 9876 and runs whatever script that URL returns. The production artifact is the output of mvn package (gwt:compile), not target/gwt/launcherDir. Keep the Maven env-dev profile, and any Spring Boot development profile that exposes the directory, out of production configurations.

  • Source maps and the sources they reference are also sensitive when produced by a production compile. Whether to publish them is a deployment decision discussed on Linkers & Bootstrap and XSRF, RPC & Server Security.

  • The code server and the Swing DevMode are development tools: they hold no user data, but they run your build, so run them as an unprivileged user and do not point them at untrusted modules.

Worked example: Bookshelf development session

This session combines the pieces on the Bookshelf scratch application: the server, the code server, an edit, an error and the recovery.

  1. Start the two processes as in the previous section, then open http://localhost:8081/ in Chrome. The stub asks the code server to compile; the first compile of the application takes about 9 s (3.9 s on a warm disk cache).

  2. Open the devtools, choose Sources and press the search shortcut to open CatalogPresenter.java. Set a breakpoint on the service.getBooks(...) line and reload: the compile is skipped (nothing changed) and the breakpoint is hit.

  3. Change a string in AppMessages.java and reload. The log shows Compiling 1 permutation, 8 new or changed types and a half-second compile.

  4. Introduce a typo (an int after the @DefaultMessage annotation). The reload still shows the previous version, and the code server log shows the errors (below).

  5. Remove the typo and reload: the next compile succeeds and the page updates.

The failing recompile of step 4 answered the request with {"moduleNames":["bookshelf"],"status":"failed"} and logged:

[INFO]          Tracing compile failure path for type 'com.example.bookshelf.client.i18n.AppMessages'
[INFO]             [ERROR] Errors in 'file:.../bookshelf-client/src/main/java/com/example/bookshelf/client/i18n/AppMessages.java'
[INFO]                [ERROR] Line 6: The annotation @Messages.DefaultMessage is disallowed for this location
[INFO]                [ERROR] Line 7: Syntax error, insert ";" to complete FieldDeclaration
[INFO]                [ERROR] Line 7: Return type for the method is missing
[INFO]          [ERROR] Aborting compile due to errors in some input files
[INFO]       [ERROR] Compiler returned false
[INFO]       [WARN] recompile failed
[INFO]       [WARN] continuing to serve previous version

The last two lines explain the most common confusion of a new SDM user: after a compile error the browser keeps running the last good build, so "my change does nothing" almost always means "read the code server’s terminal". The browser’s own Network tab shows the failing /recompile/... response.

Troubleshooting and pitfalls

Table 4. Common problems with the development loop (GWT 2.13.1)
Symptom Cause Fix

A confirm dialog: Couldn't load bookshelf from Super Dev Mode server at http://localhost:9876. Please make sure this server is ready.

The code server is not running yet, or listens on another host or port, or is bound to a different address than the page uses.

Wait for The code server is ready at ..., start it, and reload. Open the page with the same host name (the stub builds the URL from location.hostname) that the code server is reachable at.

My edit has no effect after a reload

The recompile failed and the code server continues to serve the previous version (recompile failed, continuing to serve previous version).

Read the code server terminal for [ERROR] lines and fix them; the page updates on the next successful compile.

GET /recompile/bookshelf logs Current binding properties are expanding to more than one permutation but incremental compilation requires that each compile operate on only one permutation.

A manual call (curl or a script) without the user.agent and locale properties, so the compile spans several permutations. A later request for the module’s bookshelf.nocache.js then answers 500 with a NullPointerException in Outbox.getOutputDir() because no output exists yet.

Let the stub or bookmarklet send the properties, or pass them yourself (?user.agent=safari&locale=en), or fix them with -setProperty or a development module.

/sourcemaps/... returns 404 with This module hasn't been compiled yet.

Devtools asked for a source before the first successful compile of this module.

Reload the application page so that the first compile runs.

Old behaviour persists after changing module XML, a ClientBundle or a generator, or the code server seems to use stale data

The incremental cache and the on-disk cache hold compiled data from before the change.

Open http://127.0.0.1:9876/clean (the response is Cleaned disk caches.), or stop the server and delete target/gwt/codeserver, then reload. The 2.8.0 notes mention an incremental-compile fix for changed compiler options, so keep options stable between starts.

port set in the client POM is ignored

gwt:codeserver is an aggregator goal; its configuration comes from the root POM.

Put codeserverArgs, launcherDir and logLevel in the root POM’s plugin configuration.

java.awt.HeadlessException and Error running on Swing UI thread

DevMode needs a display for its Swing window.

Use mvn gwt:codeserver plus your own server.

Unable to find main() method for Super Dev Mode code server. Hint: verify that gwt-codeserver.jar is in your classpath.

DevMode runs CodeServer by reflection and gwt-codeserver is not on the class path.

Add org.gwtproject:gwt-codeserver (managed by the gwt BOM) or use the Maven goal.

After switching to -noserver or your own server, RPC fails with a serialization policy error (see GWT-RPC)

The *.gwt.rpc serialization policy files in the module output are older than your serializable types. The Developer Guide notes that these files must be redeployed if the serializable types change.

Recompile (the code server regenerates them) and serve the module output that the code server produces; do not copy a stale directory.

The page does not use the code server at all

The page’s *.nocache.js is the production one and no bookmarklet was pressed, or session storage key was removed by closing the tab.

Use the launcherDir stub, or press Dev Mode On again on the tab.

Breakpoints in Java files never hit, or lines are off

The browser did not apply source maps (disabled in the devtools settings), or the page runs a cached old build.

Enable source maps in the devtools, hard-reload with the cache disabled, and check that the .cache.js ends with a sourceMappingURL.

A bug appears only in the production build

SDM compiles in draft mode and omits checks the JVM would perform. The Developer Guide states the principle that if it runs in development mode and compiles, it should behave the same, with rare exceptions.

Run mvn package and test the result in the target browsers before release, with -style PRETTY if you need readable stack traces.

-pl '*-client' fails with Could not find the selected project in the reactor

Wildcard project selection is a Maven 4 feature; Maven 3.9 does not support it.

Use -pl bookshelf-client or -pl :bookshelf-client.

Where older material is outdated

  • Articles and tutorials that start "development mode" with ant devmode, webAppCreator or an Eclipse launch configuration describe the classic flow. webAppCreator is deprecated and its generator classes were removed in 2.12.0, so a new project starts from a Maven archetype (see Getting Started and Build Tools: Maven & Gradle).

  • The Developer Guide page "Compiling and Debugging" still documents the DevMode options with the older defaults (the embedded Jetty serving your servlets, a GUI with a Restart Server button) and does not mention the 2.13 change; the release notes do.

  • The -sourceLevel help text and the guide say the default is 1.8, which is correct for the command-line tools; the Java 12-17 features of 2.12.0 and later need an explicit -sourceLevel 17 (the auto value resolves to the running JVM’s level).

  • The book’s hosted mode and the plugin-based flow of 2.0 to 2.6 are historical, as above.