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.
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-clientholds the code that runs in the browser: theEntryPoint, widgets, views and the module XML. Its packaging isgwt-app, which adds the GWT compilation to the Maven lifecycle. It is the only module the GWT compiler translates. -
bookshelf-sharedholds plain Java used on both sides, such as theBookclass and, later, the RPC service interfaces. It is an ordinaryjar, and also produces asourcesjar because the compiler needs the Java source, not only the class files, of everything it compiles. -
bookshelf-serveris awarwith the servlets, theweb.xmland 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 |
Apache Maven |
3.9.16 |
Used for the archetype, the GWT plugin and the Jetty plugin. The wildcard |
GWT |
2.13.1 |
Imported through the |
|
1.3.0 |
Adds the |
|
2026.6.22 |
The official page uses |
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
Source: tbroyer/gwt-maven-archetypes
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>
Source: gwt-maven-plugin — Basic usage
Three details are worth reading closely:
-
The
gwt-maven-pluginentry with<inherited>false</inherited>configures the plugin for the root project only. That is where thegwt:codeservergoal runs, because it is an aggregator goal: the plugin’s "Running and debugging" page says its configuration belongs in the root POM, not in thegwt-appmodule. -
launcherDiris the folder into which the code server writes the small bootstrap scriptbookshelf.nocache.jsthat makes the browser ask the code server for a recompile. It is placed inside the server module’stargetso that the Jetty plugin can serve it next to the host page. -
failOnErroris true, so a compilation error fails the build instead of producing a partial result.sourceLevel17 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 inToolBase, as the Developer Guide and the-helptext say), so Java 12 to 17 syntax needs an explicit-sourceLevel 17;autoresolves to the running JVM’s level, and the plugin’s<sourceLevel>default comes frommaven.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>
Source: gwt-maven-plugin — gwt:compile
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 tobookshelf/gwt/clean/clean.cssand the module injects it, which is why a bareMenuBarorDialogBoxalready 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 theclientsub-package, as if<source path="client"/>had been written. Later pages move the code intoclientandsharedsub-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 whoseonModuleLoad()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
Source: Jetty — Maven Plugin
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.
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
Source: GWT — Super Dev Mode
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.");
}
}
Source: GWT Javadoc — RootPanel
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
-
EntryPointandonModuleLoad(). The class is named in the module XML, so GWT callsonModuleLoad()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 idappof the host page, andRootPanel.get()with no argument wraps the documentbody. Widgets must be attached to a root panel to appear: an unattached widget is never rendered. The staticget(String)method returnsnullwhen the page has no element with that id (see the pitfalls table), and theRootLayoutPanelsingleton is the layout-panel alternative used by pages with a resizable layout. -
Widgets and the DOM.
TextBox,Button,Label,FlowPanelandMenuBarare 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>Source: GWT — Layout with Panels
-
Click and key handlers.
addClickHandlerandaddKeyUpHandlertake handler objects; since the compiler runs at source level 17 a lambda works wherever the interface has one method. TheRunnableshared by both handlers is ordinary Java. -
Menus. A
MenuBarholds menu items.addItem(String text, ScheduledCommand cmd)adds an item that runs the command when chosen (acom.google.gwt.user.client.Commandis aScheduledCommand), andaddItem(String text, MenuBar popup)adds an item that opens another bar: the top-level bar is horizontal and the popup is created withnew MenuBar(true), which is vertical. The bar stylesheet comes from the Clean theme. TheMenuBarrenders a hidden<input>for keyboard focus, sodocument.querySelector('input')finds that first. -
Where this runs. All of this is client code.
Bookis 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.
Bookshelf
[ Catalogue v ]
[ Title ] [ Author ] [Add book]
1 book in the catalogue.
Dune (Herbert)
Source: GWT Javadoc — MenuBar
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) |
|---|---|---|
|
A simple application compiled to JavaScript and served without an application server. |
|
|
Widgets, source and CSS of the toolkit, with a server that only hosts static files. |
|
|
A replica of a desktop email client’s UI, using widgets and history management. |
|
|
Widgets, shared code and GWT-RPC. |
the same pair with |
|
RequestFactory proxies, locators and services. |
the same pair with |
|
Loading data from a URL; the server hosts the app and a JSON file. |
the same pair with |
|
Bean Validation (JSR 380) on shared client and server types. |
the same pair with |
|
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. |
|
Source: GWT repository — samples
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".
Source: GWT — Examples: Samples
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 generatedApp.java(the tutorial’s names areStockWatcher.javaandStockWatcher.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,addStyleDependentNameandsetStyleName, 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 |
|
Coding Client side |
Widgets and JRE Emulation & Shared Code (what Java can be used on the client). |
Debugging |
|
Styling |
|
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 theEntryPointiscom.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 |
|---|---|---|
|
The wildcard |
Use |
Jetty fails to start with |
The code server has not created |
Start |
A confirm dialog: |
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 |
A blank page and a console error |
|
Make the id in the page match the string in the code, or use |
The browser shows "failed" with "Try Again" and the old behaviour keeps running |
The last save has a compile error; the code server logs |
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. |
|
The |
Run |
INFO line |
The jar dependency is not a GWT library; the plugin uses the |
None: it is information. |
The sample projects cannot resolve |
The |
Pass |
Text typed by a user appears as markup |
|
Use |
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.