Getting Started

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 and the modular-webapp archetype 2026.6.22, which generates a build with source and release level 17 and Jakarta Servlet 6.0.0, checked on 2026-10-08. Every example was generated, compiled and run on that stack with Maven 3.9.16 and JDK 21. The page is about build/tooling (the Maven modules and the development loop) and about the first pieces of client code (compiled to JavaScript) and server code (a servlet container) that a new project contains. The goal is a running, reloadable application in a few minutes and a clear picture of where every file goes. The running scenario of the section, the Bookshelf (books, authors, reviews and loans), starts here as a three-module project. The pages that follow in the section take each piece further, beginning with Introduction & Architecture; Build Tools: Maven & Gradle is the full build reference.

What you are building

A GWT application is Java source that the GWT compiler translates into JavaScript, plus an ordinary HTML page that loads that JavaScript. The modular-webapp archetype splits the work into three Maven modules, which is the layout the official getting-started page uses since GWT 2.11.0, when webAppCreator was deprecated in favour of this archetype by Thomas Broyer.

flowchart LR shared["bookshelf-shared
jar plus sources jar"] client["bookshelf-client
gwt-app: compiled to JavaScript"] server["bookshelf-server
war: servlets and static files"] shared --> client shared --> server client -->|"env-prod: client war is overlaid"| server client -.->|"env-dev: files from launcherDir"| server
  • bookshelf-client holds the code that runs in the browser: the EntryPoint, widgets, views and the module XML. Its packaging is gwt-app, which adds the GWT compilation to the Maven lifecycle. It is the only module the GWT compiler translates.

  • bookshelf-shared holds plain Java used on both sides, such as the Book class and, later, the RPC service interfaces. It is an ordinary jar, and also produces a sources jar because the compiler needs the Java source, not only the class files, of everything it compiles.

  • bookshelf-server is a war with the servlets, the web.xml and the static files, among them the host page. Its Jetty plugin configuration is what you run during development.

Everything in the client and shared modules must stay inside the subset of Java and the JRE that GWT can translate; the server module is unrestricted Java. That boundary is the central rule of GWT and is the subject of Introduction & Architecture and JRE Emulation & Shared Code.

Prerequisites

The official getting-started page states that GWT 2.13.1 runs on Java 11 or later, and asks for Apache Maven to run the archetype. The release notes of 2.13.0 add that 2.13.0 is likely to be the last release where the compiler and the development tools run on Java 11, so a Java 17 or 21 JDK is the safe choice. The generated project itself uses source and release level 17, and the Jetty 12 plugin it runs the server with needs Java 17; the official samples say the same ("Java 17+ is required to run the server, although GWT compilation can be done with Java 11+"). A modern browser is needed for Super Dev Mode: the compiler only generates code for two user.agent values (gecko1_8 and safari), which covers Chrome, Firefox, Edge and Safari.

Tool Version used on this page Notes

JDK

21 (Temurin, JetBrains Runtime)

11 or later runs the GWT tools; 17 or later is needed for the generated server module and release 17.

Apache Maven

3.9.16

Used for the archetype, the GWT plugin and the Jetty plugin. The wildcard -pl form of the official page does not work on 3.9 (see the pitfalls table).

GWT

2.13.1

Imported through the org.gwtproject:gwt BOM, so no individual GWT artifact carries a version.

net.ltgt.gwt.maven:gwt-maven-plugin

1.3.0

Adds the gwt-app and gwt-lib packagings and the codeserver and compile goals.

net.ltgt.gwt.archetypes:modular-webapp

2026.6.22

The official page uses -DarchetypeVersion=LATEST.

Browser

Chrome

Any current browser works; the developer tools' Sources tab shows the Java files in Super Dev Mode.

Check the tools before starting:

java -version
mvn -version

The IDE set-up (Eclipse, IntelliJ IDEA, VS Code) is optional for this page, since everything runs from the command line; it is covered by Tooling & IDE Support.

Generate the project

The archetype is run with mvn archetype:generate. The official page runs it interactively with -DarchetypeVersion=LATEST. A pinned version and -DinteractiveMode=false make the result reproducible, and the parameters are then passed on the command line. The archetype’s extra parameter is module-short-name, which becomes the name of the compiled bookshelf.nocache.js script and of the folder that holds it:

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 \
  -Dversion=1.0-SNAPSHOT \
  -DinteractiveMode=false
cd bookshelf

The generated project has these files (the sample greeting service is part of the archetype; it is removed further down):

bookshelf/
  pom.xml
  bookshelf-client/
    pom.xml
    src/main/module.gwt.xml
    src/main/java/com/example/bookshelf/App.java
  bookshelf-shared/
    pom.xml
    src/main/java/com/example/bookshelf/
      FieldVerifier.java  GreetingResponse.java  GreetingService.java  GreetingServiceAsync.java
  bookshelf-server/
    pom.xml
    src/main/java/com/example/bookshelf/GreetingServiceImpl.java
    src/main/jettyconf/context.xml
    src/main/webapp/
      index.html  bookshelf.css  favicon.ico  WEB-INF/web.xml

The older tools are gone: the official page says webAppCreator is deprecated from 2.11.0, and the 2.12.0 release notes list the removal of the ApplicationCreator and ProjectCreator main classes. The StockWatcher tutorial described below still names webAppCreator and the Eclipse plugin in its first step, so use that step only as a description of what a new project contains.

The generated modules

The root POM

The root POM is the one place where versions live. It imports the GWT BOM, pins the Jakarta Servlet API, and sets the plugin versions:

<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>
  <plugins>
    <plugin>
      <groupId>net.ltgt.gwt.maven</groupId>
      <artifactId>gwt-maven-plugin</artifactId>
      <inherited>false</inherited>
      <configuration>
        <launcherDir>${basedir}/bookshelf-server/target/gwt/launcherDir</launcherDir>
        <warDir>${basedir}/bookshelf-server/target/gwt/launcherDir</warDir>
        <devmodeArgs>
          <arg>-noserver</arg>
        </devmodeArgs>
      </configuration>
    </plugin>
  </plugins>
  <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>
      <!-- maven-compiler-plugin 3.15.0 with release 17, jetty-ee10-maven-plugin 12.1.10, surefire, war and source plugins -->
    </plugins>
  </pluginManagement>
</build>
<modules>
  <module>bookshelf-client</module>
  <module>bookshelf-shared</module>
  <module>bookshelf-server</module>
</modules>

Three details are worth reading closely:

  • The gwt-maven-plugin entry with <inherited>false</inherited> configures the plugin for the root project only. That is where the gwt:codeserver goal runs, because it is an aggregator goal: the plugin’s "Running and debugging" page says its configuration belongs in the root POM, not in the gwt-app module.

  • launcherDir is the folder into which the code server writes the small bootstrap script bookshelf.nocache.js that makes the browser ask the code server for a recompile. It is placed inside the server module’s target so that the Jetty plugin can serve it next to the host page.

  • failOnError is true, so a compilation error fails the build instead of producing a partial result. sourceLevel 17 tells the GWT compiler which Java language level the sources may use. The command-line compiler’s default is 1.8 (the default argument registered in ToolBase, as the Developer Guide and the -help text say), so Java 12 to 17 syntax needs an explicit -sourceLevel 17; auto resolves to the running JVM’s level, and the plugin’s <sourceLevel> default comes from maven.compiler.source.

The client module

The client POM depends on the shared module twice, as a jar (for javac) and with the sources classifier (for the GWT compiler), and on gwt-user and gwt-dev. The plugin configuration names the module:

<artifactId>bookshelf-client</artifactId>
<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>org.gwtproject</groupId>
    <artifactId>gwt-user</artifactId>
  </dependency>
  <dependency>
    <groupId>org.gwtproject</groupId>
    <artifactId>gwt-dev</artifactId>
  </dependency>
</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>net.ltgt.gwt.maven</groupId>
      <artifactId>gwt-maven-plugin</artifactId>
      <configuration>
        <moduleName>com.example.bookshelf.App</moduleName>
        <moduleShortName>bookshelf</moduleShortName>
      </configuration>
    </plugin>
  </plugins>
</build>

moduleName is the fully qualified name of the GWT module (the App module in package com.example.bookshelf), and moduleShortName is the name given to the output script, so the browser loads bookshelf/bookshelf.nocache.js. The plugin documentation describes it as "the short name of the module, used to name the output .nocache.js file".

The shared and server modules

The shared module’s only dependency is gwt-servlet-jakarta with provided scope (it needs the RPC interfaces) and the maven-source-plugin to attach the sources jar at the package phase. The server module is a war that depends on the shared module and on gwt-servlet-jakarta, and on jakarta.servlet-api as provided. Its two profiles are the key to the development loop:

<profiles>
  <profile>
    <id>env-prod</id>
    <activation><activeByDefault>true</activeByDefault></activation>
    <dependencies>
      <dependency>
        <groupId>${project.groupId}</groupId>
        <artifactId>bookshelf-client</artifactId>
        <version>${project.version}</version>
        <type>war</type>
        <scope>runtime</scope>
      </dependency>
    </dependencies>
  </profile>
  <profile>
    <id>env-dev</id>
    <activation>
      <property><name>env</name><value>dev</value></property>
    </activation>
    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <groupId>org.eclipse.jetty.ee10</groupId>
            <artifactId>jetty-ee10-maven-plugin</artifactId>
            <configuration>
              <webApp>
                <resourceBases>
                  <resourceBase>${basedir}/src/main/webapp</resourceBase>
                  <resourceBase>${project.build.directory}/gwt/launcherDir/</resourceBase>
                </resourceBases>
              </webApp>
            </configuration>
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
  </profile>
</profiles>

In the default env-prod profile the server war overlays the compiled client war, so one deployable archive contains the host page, the JavaScript and the servlets. With -Denv=dev (or -Penv-dev) the env-dev profile is active instead: the client dependency disappears, and Jetty serves the static files of the server module plus whatever the code server wrote into launcherDir. The archetype’s README explains it the same way: env-dev "configures the Jetty plugin and removes the dependency on the client module".

The module XML, the host page and the EntryPoint

Three files connect the Maven build to the browser.

The module XML

The module XML is bookshelf-client/src/main/module.gwt.xml. It is a source file for the plugin, not the finished module:

<?xml version="1.0" encoding="UTF-8"?>
<module>
  <inherits name="com.google.gwt.user.User" />
  <inherits name="com.google.gwt.user.theme.clean.Clean" />

  <source path="" />

  <entry-point class="com.example.bookshelf.App" />

  <!-- Only support recent browsers -->
  <set-property name="user.agent" value="gecko1_8,safari" />
</module>

The gwt:generate-module goal processes it into target/classes/com/example/bookshelf/App.gwt.xml, adding the rename-to attribute from moduleShortName. This is the generated copy, as it was produced by the build:

<module rename-to="bookshelf">
  <inherits name="com.google.gwt.user.User"/>
  <inherits name="com.google.gwt.user.theme.clean.Clean"/>
  <source path=""/>
  <entry-point class="com.example.bookshelf.App"/>
  <set-property name="user.agent" value="gecko1_8,safari"/>
</module>

What each line does:

  • <inherits name="com.google.gwt.user.User"/> pulls in the core of the toolkit: widgets, event handlers, the DOM wrappers and RPC types. A class from a package that no inherited module covers fails to compile with "No source code is available".

  • <inherits name="com.google.gwt.user.theme.clean.Clean"/> adds the "Clean" theme. The build copies its stylesheet to bookshelf/gwt/clean/clean.css and the module injects it, which is why a bare MenuBar or DialogBox already has a look.

  • <source path=""/> makes the package of the module file, and every package below it, translatable source. The Developer Guide says each <source> element adds a sub-package of the package that holds the module file, so the empty path means "this package and its sub-packages". Without any <source> element, GWT implicitly adds the client sub-package, as if <source path="client"/> had been written. Later pages move the code into client and shared sub-packages and list them explicitly (see the full project structure below), so nothing outside them can be translated by accident.

  • <entry-point class="com.example.bookshelf.App"/> names the class whose onModuleLoad() runs when the script has loaded.

  • <set-property name="user.agent" value="gecko1_8,safari"/> restricts the permutations to the two supported values, so a compile produces two JavaScript variants instead of more.

The reference for every element, and for inheriting, overriding and super-sources, is Project Structure & Modules.

The host page

The host page is the HTML page that loads the compiled script. The generated one is bookshelf-server/src/main/webapp/index.html. The important parts are a standards-mode doctype, one <script> that loads bookshelf/bookshelf.nocache.js, and elements with ids that the code attaches widgets to. This version is the one the page uses from here on (the archetype’s own file also contains a table for the greeting sample):

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="bookshelf.css">
    <title>Bookshelf</title>
    <script src="bookshelf/bookshelf.nocache.js"></script>
  </head>
  <body>
    <noscript>This application needs JavaScript.</noscript>
    <h1>Bookshelf</h1>
    <div id="app"></div>
  </body>
</html>

The *.nocache.js file is a tiny bootstrap that is never cached: it decides, from the browser’s properties, which permutation to load, and then loads the matching *.cache.js file, which is cacheable forever because its name is a hash of its content. Compiler & Compilation explains that selection in detail (see also Linkers & Bootstrap). In Super Dev Mode the same file name is served from launcherDir and asks the code server for a fresh compile instead.

The EntryPoint

An entry point is a class that implements com.google.gwt.core.client.EntryPoint, which has one method, onModuleLoad(). The Javadoc says the method is "called automatically by loading a module that declares an implementing class as an entry point", which happens once, in the browser, after the script has loaded. A module may name several <entry-point> classes, including ones from inherited modules; the Developer Guide says they are called sequentially in the order they appear in the module file, each as soon as the previous onModuleLoad() returns. The archetype’s App class builds a button, a text field and a dialog for a greeting call to the server. The Bookshelf replaces it with the first real screen below.

The development loop

GWT has no run-the-Java-on-the-server development mode any more: the browser runs the compiled JavaScript from the start, and the Super Dev Mode code server recompiles it on demand, in seconds. The classic Development Mode and its browser plugins are dead, and the 2.13.0 release notes say the DevMode server now only serves static files by default. Two processes run side by side during development: the code server and the application server. They run in two terminals, from the root of the project.

Start the code server

mvn gwt:codeserver -pl bookshelf-client -am

-pl limits the build to the named module and -am ("also make") adds the modules it depends on, here bookshelf-shared, so the code server sees the shared sources. The code server prints, on the first start:

[INFO] --- gwt:1.3.0:codeserver (default-cli) @ bookshelf ---
[INFO] Ignoring com.example.bookshelf:bookshelf-shared:jar:1.0-SNAPSHOT; neither a gwt-lib or jar:sources; Did you forget to use <type>gwt-lib</type> in the dependency declaration?
[INFO] Turning off precompile in incremental mode.
[INFO] Super Dev Mode starting up
[INFO]    Loading Java files in com.example.bookshelf.App.
[INFO]    Module setup completed in 6077 ms
[INFO] The code server is ready at http://127.0.0.1:9911/

The "Ignoring …​ bookshelf-shared:jar" line is information, not a problem: the plugin uses the sources classifier dependency and the reactor module’s source folder instead, so shared code changes are picked up immediately. The server listens on 127.0.0.1:9876 by default; the excerpt above comes from a run that changed the port to 9911 with <codeserverArgs> in the root POM (shown below) to avoid a clash with other processes. The code server is the long-running process: leave it open.

The official page and the archetype README write the module selector as -pl *-client -am. That form does not work on Maven 3.9.16:

[ERROR] Could not find the selected project in the reactor: *-client

Use the explicit module name (-pl bookshelf-client), the groupId-less form -pl :bookshelf-client (also verified), or leave -pl out and let the reactor build everything; the archetype README says -pl and -am are "not strictly necessary", they only keep Maven from building the client when you work on the server and the other way around. The official samples' own README files use the explicit form (-pl mail-client -am).

Start the application server

In a second terminal, start Jetty with the env-dev profile. The prefix jetty resolves to the jetty-ee10-maven-plugin configured in the POM:

mvn jetty:run -pl bookshelf-server -am -Denv=dev

Start it after the code server has printed "ready". The code server creates launcherDir, and Jetty is configured to serve it; if it does not exist yet, Jetty stops with Bad resourceBases: [...]: Does not exist: file:///.../target/gwt/launcherDir/. The same happens right after a mvn clean, which deletes the folder. When Jetty is up, open http://localhost:8080 (add -Djetty.http.port with another number to change the port) and the application appears. The first load is slow because the code server compiles the module on the first request: in my run, "Compile completed" took 3.8 seconds.

The jetty plugin is configured with <scan>1</scan>, so Jetty reloads when the server classes or web.xml change. Server code changes need no restart of the code server, and client code changes need no restart of Jetty.

Change code and refresh

The loop is: edit a Java file in the client or the shared module, save, and reload the browser tab. Nothing is built in the background; the reload is what triggers the compile.

flowchart TD edit["Edit and save a Java file
in bookshelf-client or bookshelf-shared"] --> reload["Reload the browser tab"] reload --> stub["Jetty serves bookshelf.nocache.js
from launcherDir"] stub --> ask["The script requests /recompile
from the code server
port 9876 by default"] ask --> compile{"Does the module
still compile?"} compile -->|"yes"| js["Incremental compile: only changed types
JavaScript plus source maps"] compile -->|"no"| err["Errors in the code server log
the previous version keeps being served"] js --> run["The page runs the new JavaScript"] run --> edit err --> edit

This was measured on the Bookshelf: a one-line change to a status message and a reload gave a recompile of five changed types in about 0.47 seconds, against 3.8 seconds for the first compile. The code server log for that reload:

[INFO] GET /recompile/bookshelf
[INFO]    Job com.example.bookshelf.App_1_2
[INFO]       binding: user.agent=safari
[INFO]       Compiling module com.example.bookshelf.App
[INFO]          Unification traversed 696 fields and methods and 424 types. 5 are considered part of the current module ...
[INFO]          Compiling 1 permutation
[INFO]             Linking per-type JS with 5 new/changed types.
[INFO]             Source Maps Enabled
[INFO]          Compilation succeeded -- 0,392s
[INFO]       0,471s total -- Compile completed

Two things make this fast. The recompile is incremental, which is on by default in the code server and works on a single permutation: the browser tells the code server its user.agent binding (here safari), so only that permutation is compiled. A request with no binding gives Current binding properties are expanding to more than one permutation but incremental compilation requires that each compile operate on only one permutation. and nothing is compiled, which is what you see if you call /recompile/<module> with curl. And when nothing changed, the log says skipped compile because no input files have changed.

A compile error does not break the page. After saving a broken line (int broken = "text";) the reload gave this in the code server log, and the browser showed a "failed" message with a "Try Again" button and kept the previous version running:

[ERROR] Line 66: Type mismatch: cannot convert from String to int
[ERROR] Compiler returned false
[WARN] recompile failed
[WARN] continuing to serve previous version

Debug in the browser

Source maps are on by default since 2.12.0 (the log shows "Source Maps Enabled"), so the developer tools show the Java files themselves. In Chrome, open the developer tools, the Sources tab, press the Ctrl key (Cmd on macOS) together with P, type the file name, for instance App.java, and set a breakpoint in the Java source. The same page of the official getting-started guide shows the steps. Development Mode & Debugging covers breakpoints, logging and the other tools.

Production build

mvn package runs the GWT compiler. It writes the JavaScript into the client module and overlays it into the server war:

mvn clean package

On the Bookshelf this takes about 7 seconds in total, of which the GWT compile is 4 to 5 seconds, and ends with BUILD SUCCESS for the shared, client and server modules. The server war bookshelf-server/target/bookshelf-server-1.0-SNAPSHOT.war contains index.html, WEB-INF/web.xml, and the compiled application under bookshelf/: bookshelf.nocache.js, one *.cache.js file per permutation (two here: gecko1_8 and safari), compilation-mappings.txt, clear.cache.gif and the Clean theme’s clean.css. The war can be deployed to any Jakarta EE 9 or newer servlet container. To try it without installing one, run it through the Jetty plugin; the official samples use the same command:

mvn jetty:run-war -pl bookshelf-server -am -Djetty.http.port=8080

Requests for / and for /bookshelf/bookshelf.nocache.js returned 200 in my run. mvn package is also what a CI job runs, and mvn verify and mvn install additionally run the tests and make the modules available to other builds.

Changing the port of the code server

The default code server port is 9876. When something else uses it, set a different one for the code server in the root POM. The generated bookshelf.nocache.js stub is written with the same port, because it is generated from the code server’s configuration:

<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>9911</arg>
    </codeserverArgs>
  </configuration>
</plugin>

With this change the log says The code server is ready at http://127.0.0.1:9911/ and the bootstrap script contains var serverUrl = 'http://' + hostName + ':9911';. The code server binds to 127.0.0.1 by default, so it is not reachable from other machines; keep it that way, because it can compile and serve your source.

A first Bookshelf screen

The generated sample talks to a greeting service. The Bookshelf first screen replaces it with something that needs no server at all: a small form that adds books to a list and a message that shows the state. Remove the sample files, then add one shared class, change the host page and write the entry point.

rm bookshelf-server/src/main/java/com/example/bookshelf/GreetingServiceImpl.java
rm bookshelf-shared/src/main/java/com/example/bookshelf/{FieldVerifier,GreetingResponse,GreetingService,GreetingServiceAsync}.java
# also delete the <servlet> and <servlet-mapping> entries for greetServlet from bookshelf-server/src/main/webapp/WEB-INF/web.xml

The shared class is plain Java. GWT-RPC needs the empty constructor and Serializable (later pages use them), and the class compiles on both sides:

package com.example.bookshelf;

import java.io.Serializable;

public class Book implements Serializable {
    private String title;
    private String author;

    Book() {
        // needed by GWT-RPC serialisation
    }

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }
}

The entry point creates the widgets, attaches them to the page through RootPanel, wires a click handler and a key handler, and adds a menu:

package com.example.bookshelf;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import java.util.ArrayList;
import java.util.List;

public class App implements EntryPoint {

    private final List<Book> books = new ArrayList<>();
    private final FlowPanel list = new FlowPanel();
    private final Label status = new Label();

    @Override
    public void onModuleLoad() {
        TextBox title = new TextBox();
        title.getElement().setAttribute("placeholder", "Title");
        TextBox author = new TextBox();
        author.getElement().setAttribute("placeholder", "Author");
        Button add = new Button("Add book");

        Runnable addBook = () -> {
            String t = title.getText().trim();
            String a = author.getText().trim();
            if (t.isEmpty() || a.isEmpty()) {
                status.setText("Both the title and the author are required.");
                return;
            }
            books.add(new Book(t, a));
            // Label.setText escapes the text: user input never reaches the DOM as HTML
            list.add(new Label(t + " (" + a + ")"));
            showCount();
            title.setText("");
            author.setText("");
            title.setFocus(true);
        };
        add.addClickHandler(event -> addBook.run());
        author.addKeyUpHandler(event -> {
            if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
                addBook.run();
            }
        });

        MenuBar catalogue = new MenuBar(true);
        catalogue.addItem("Clear catalogue", () -> {
            books.clear();
            list.clear();
            showCount();
        });
        MenuBar menu = new MenuBar();
        menu.addItem("Catalogue", catalogue);

        RootPanel root = RootPanel.get("app");
        root.add(menu);
        root.add(title);
        root.add(author);
        root.add(add);
        root.add(status);
        root.add(list);
        showCount();
    }

    private void showCount() {
        status.setText(books.isEmpty()
                ? "The catalogue is empty."
                : books.size() + (books.size() == 1 ? " book" : " books") + " in the catalogue.");
    }
}

The same code, with Label.setText for the text, is safe by construction: in my run, the author <b>Herbert</b> was shown literally as Dune (<b>Herbert</b>) and no element was created. The status message uses setText too and no Window.alert.

What the code does

  • EntryPoint and onModuleLoad(). The class is named in the module XML, so GWT calls onModuleLoad() once after the script loads. All start-up work goes there; the constructor and field initialisers of the class run before it, and the objects exist only in the browser.

  • RootPanel. RootPanel.get("app") wraps the element with id app of the host page, and RootPanel.get() with no argument wraps the document body. Widgets must be attached to a root panel to appear: an unattached widget is never rendered. The static get(String) method returns null when the page has no element with that id (see the pitfalls table), and the RootLayoutPanel singleton is the layout-panel alternative used by pages with a resizable layout.

  • Widgets and the DOM. TextBox, Button, Label, FlowPanel and MenuBar are Java objects that each own a DOM element. The page after the first click looked like this in the browser (attributes shortened):

    <div id="app">
      <div role="menubar" class="gwt-MenuBar gwt-MenuBar-horizontal"> ... <td class="gwt-MenuItem">Catalogue</td> ... </div>
      <input type="text" class="gwt-TextBox" placeholder="Title">
      <input type="text" class="gwt-TextBox" placeholder="Author">
      <button type="button" class="gwt-Button">Add book</button>
      <div class="gwt-Label">The catalogue is empty.</div>
      <div></div>
    </div>
  • Click and key handlers. addClickHandler and addKeyUpHandler take handler objects; since the compiler runs at source level 17 a lambda works wherever the interface has one method. The Runnable shared by both handlers is ordinary Java.

  • Menus. A MenuBar holds menu items. addItem(String text, ScheduledCommand cmd) adds an item that runs the command when chosen (a com.google.gwt.user.client.Command is a ScheduledCommand), and addItem(String text, MenuBar popup) adds an item that opens another bar: the top-level bar is horizontal and the popup is created with new MenuBar(true), which is vertical. The bar stylesheet comes from the Clean theme. The MenuBar renders a hidden <input> for keyboard focus, so document.querySelector('input') finds that first.

  • Where this runs. All of this is client code. Book is shared code: the compiler translates it to JavaScript for the client, and javac compiles it for the server.

Save the files and reload the browser tab: the screen appears, the status shows The catalogue is empty., and an empty submit shows Both the title and the author are required.. I verified adding a book (1 book in the catalogue. and the entry in the list) and the Clear catalogue menu item, which empties the list and shows the empty message again.

The first Bookshelf screen, as run on Super Dev Mode
Bookshelf
[ Catalogue v ]
[ Title ] [ Author ] [Add book]
1 book in the catalogue.
Dune (Herbert)

Security notes for the first screen

The sample uses plain text everywhere. A few of the widgets' methods interpret their argument as HTML: the Button(String) constructor takes HTML (it is annotated as a SafeHtml-sensitive parameter), HTML.setHTML(String) and the addItem overload with an asHTML flag set to true do as well. Never pass user data to them concatenated; use setText, or build the markup with SafeHtmlBuilder and pass a SafeHtml. The archetype’s own sample does that for the server’s reply. Authorisation never belongs in client code: anything the browser does can be replayed by a script, so the server module checks who is allowed to do what. SafeHtml & XSS and XSRF, RPC & Server Security cover cross-site scripting, XSRF and the server-side checks.

The official samples

The samples folder of the GWT repository and of the SDK has eight sample applications, which since 2.13.0 are built with Maven. They are the quickest way to see larger applications. Six of them (Mail, Showcase, DynaTable, DynaTable-RF, JSON and Validation) use the same client and server module layout as this page, generated with the modular-webapp or modular-requestfactory archetype, and run with the same two commands. In the 2.13.1 tag every sample’s POM sets gwt.version to HEAD-SNAPSHOT, which resolves against the GWT snapshot server, so pass the release explicitly. I built the Mail sample with mvn -Dgwt.version=2.13.1 package, which ended with BUILD SUCCESS for the client and server modules.

Sample What it shows Run (from the sample’s folder)

hello

A simple application compiled to JavaScript and served without an application server.

mvn war:exploded gwt:devmode, then http://localhost:8888/Hello.html

showcase

Widgets, source and CSS of the toolkit, with a server that only hosts static files.

mvn gwt:devmode -pl showcase-client -am, then mvn jetty:run -pl showcase-server -am -Denv=dev (the README notes this one uses devmode, not the code server)

mail

A replica of a desktop email client’s UI, using widgets and history management.

mvn gwt:codeserver -pl mail-client -am, then mvn jetty:run -pl mail-server -am -Denv=dev

dynatable

Widgets, shared code and GWT-RPC.

the same pair with dynatable-client and dynatable-server

dynatablerf

RequestFactory proxies, locators and services.

the same pair with dynatablerf-client and dynatablerf-server

json

Loading data from a URL; the server hosts the app and a JSON file.

the same pair with json-client and json-server

validation

Bean Validation (JSR 380) on shared client and server types.

the same pair with validation-client and validation-server

mobilewebapp

A mobile app with local storage, rebind rules for form factors, a cache-manifest linker and App Engine. Its README calls it a legacy application that cannot run on modern Java and is limited to Java 11.

mvn install, then the code server and mvn appengine:devserver -pl mobilewebapp-server -Denv=dev

For a production build, the same READMEs run mvn install and then mvn jetty:run-war -pl <sample>-server -am, or deploy the war to a servlet container. The official Examples page lists the Showcase and Mail applications as the two samples to look at first, and a list of third-party projects built with GWT, which it says are "not endorsed by Google".

The StockWatcher tutorial

The official tutorial "Build a GWT App" builds StockWatcher, a client-side-only stock price table, in nine pages: introduction, create, design, build the UI, manage events, coding client side, debugging, styling and compilation. It is a good second step after this page because each page adds one idea to one application. It needs adjusting for 2.13:

  • The create step uses the Google Plugin for Eclipse or webAppCreator; use the archetype described above instead and write the class in the generated App.java (the tutorial’s names are StockWatcher.java and StockWatcher.html).

  • The tutorial talks about development mode in the classic sense (running the Java code in a browser plugin). Super Dev Mode, started as above, replaces it, and the debugging page’s Eclipse steps are replaced by the source maps in the browser tools.

  • The styling step’s overview lists addStyleName, addStyleDependentName and setStyleName, which still exist; check each tutorial snippet against the Javadoc of 2.13.1 before relying on it.

The Bookshelf pages of the section take the same path with the current APIs: widgets, events and handlers, styling, debugging, compilation. The server-side parts that the tutorial leaves to other tutorials (GWT-RPC, JSON) are covered by Client–Server Communication, GWT-RPC and RequestBuilder, JSON & XML.

Tutorial page Where it appears in this section

Introduction

This page and Introduction & Architecture.

Create

This page (archetype, generated modules, running).

Design

The Bookshelf screen design, written up with Widgets and Panels & Layout.

Build the UI

Widgets, Panels & Layout and UiBinder.

Manage Events

Events & Handlers.

Coding Client side

Widgets and JRE Emulation & Shared Code (what Java can be used on the client).

Debugging

Development Mode & Debugging.

Styling

Styling: CSS & GSS and ClientBundle & Images.

Compilation

Compiler & Compilation, including deferred binding.

The full Bookshelf project structure

By the end of the section the Bookshelf has the layout below, the same three Maven modules as the generated project, and it was built and tested on 2.13.1 (the project passes mvn verify with nine tests). Source and generated paths are marked; every file belongs to one of the three modules.

bookshelf/                                   (root POM: BOM, plugin versions, launcherDir)
  bookshelf-shared/                          (jar + sources jar: compiled for client and server)
    src/main/java/com/example/bookshelf/shared/
      Book.java  BookPage.java  BookNotFoundException.java
      CatalogService.java  CatalogServiceAsync.java      (GWT-RPC interfaces, XSRF-protected)
  bookshelf-client/                          (gwt-app: compiled to JavaScript)
    src/main/module.gwt.xml                  (inherits, source paths, entry point, properties)
    src/main/java/com/example/bookshelf/client/
      App.java  ClientFactory.java           (EntryPoint and wiring)
      activity/  place/                      (Activities and Places: MVP and history tokens)
      view/                                  (UiBinder views, cell widgets, editors)
      i18n/                                  (AppMessages)
      resources/                             (ClientBundle and GSS)
      interop/                               (JsInterop)
    src/main/resources/com/example/bookshelf/client/
      view/*.ui.xml  i18n/AppMessages_*.properties  resources/app.gss
    src/test/java/.../BookshelfGwtTest.java  CatalogPresenterTest.java  SpanishMessagesGwtTest.java
  bookshelf-server/                          (war: servlets and static files)
    src/main/java/com/example/bookshelf/server/
      CatalogServiceImpl.java  CatalogRepository.java  SessionFilter.java  BooksRestServlet.java
    src/main/jettyconf/context.xml
    src/main/webapp/
      index.html  bookshelf.css  WEB-INF/web.xml
    src/test/java/.../CatalogRepositoryTest.java

Two things change compared with the generated project:

  • The module XML lists the sub-packages explicitly, with <source path="client"/> and <source path="shared"/>, and the EntryPoint is com.example.bookshelf.client.App. With explicit paths the server package can never be translated by accident.

  • The client module XML also inherits further GWT modules (com.google.gwt.i18n.I18N, com.google.gwt.place.Place, com.google.gwt.activity.Activity, com.google.gwt.editor.Editor) for the features of the later pages.

Pitfalls

Symptom or message Cause Fix

[ERROR] Could not find the selected project in the reactor: *-client

The wildcard -pl form used by the official page and the archetype README is not accepted by Maven 3.9.16.

Use -pl bookshelf-client or -pl :bookshelf-client, or leave -pl out.

Jetty fails to start with Bad resourceBases: [...]: Does not exist: file:///.../target/gwt/launcherDir/

The code server has not created launcherDir yet (it was not started first, or mvn clean removed it).

Start gwt:codeserver, wait for "The code server is ready", then start jetty:run.

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 page was opened while the code server is not running (or the bootstrap script points to another port).

Start the code server, check its port against the one in bookshelf.nocache.js, and accept the dialog.

A blank page and a console error Uncaught TypeError: Cannot read properties of null (reading 'add...')

RootPanel.get("app") returned null because the host page has no element with that id (I renamed the div to reproduce it). The member name in the message is obfuscated and differs by browser.

Make the id in the page match the string in the code, or use RootPanel.get() for the body.

The browser shows "failed" with "Try Again" and the old behaviour keeps running

The last save has a compile error; the code server logs [ERROR] Line 66: Type mismatch: cannot convert from String to int and continuing to serve previous version.

Read the code server log, fix the error and reload.

Your change does not appear

The edit was in the server module (needs Jetty’s scan or a restart) or a different tab or cached host page is loaded. Nothing is rebuilt until the page is reloaded.

Reload the tab; for server classes wait for the Jetty scan.

No source code is available for type com.example.bookshelf.shared.Book; did you forget to inherit a required module? when running mvn test on the client with -am

The sources jar of the shared module is created only at the package phase, so the GWT compiler has no Java source for it.

Run mvn verify or mvn install, not only mvn test.

INFO line Ignoring com.example.bookshelf:bookshelf-shared:jar:1.0-SNAPSHOT; neither a gwt-lib or jar:sources...

The jar dependency is not a GWT library; the plugin uses the sources classifier dependency instead.

None: it is information.

The sample projects cannot resolve HEAD-SNAPSHOT

The gwt.version property in the 2.13.1 samples points at the snapshot server.

Pass -Dgwt.version=2.13.1.

Text typed by a user appears as markup

HTML.setHTML, the Button(String) constructor or asHTML menu items were given unescaped user data.

Use setText, or build a SafeHtml with SafeHtmlBuilder.

Notes on older material

Older books and tutorials, including the 2007 first edition of GWT in Action (GWT 1.3 and 1.4), start from different tooling: command-line project creator tools or an IDE wizard, a development "hosted mode" browser that runs the Java code, a single public folder and a .gwt.xml file at the root of the source tree, and Constants files for internationalisation (see Internationalization and Styling: CSS & GSS for the current approach, and Logging for client logging). The ideas still hold: a module, an EntryPoint whose onModuleLoad() attaches widgets to a RootPanel, MenuBar and Command for menus, and a host page with a nocache.js script. The tooling does not: use the archetype, Maven and Super Dev Mode instead, the handler-based event API instead of listeners, and jakarta servlets. A second edition of the book appeared in 2013, and was not consulted for this page.