JRE Emulation & Shared Code
|
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 (released 2026-06-19), gwt-maven-plugin 1.3.0 (net.ltgt.gwt.maven), JDK 21 running the compiler with -sourceLevel 17, and the Jakarta Servlet 6 server of the Bookshelf scenario, checked on 2026-10-08.
It concerns client code (the Java that the compiler translates to JavaScript and the library GWT translates it against), shared code (compiled twice, once by javac for the server and once by the GWT compiler for the browser) and build settings (-sourceLevel, jre.checks.*, the module XML).
Every behaviour in the tables was observed by compiling a probe module with the 2.13.1 compiler, loading it in headless Chrome and comparing it with the same statements run on a JDK 21 JVM; the exact outputs and compiler messages are quoted as they were produced.
What "emulation" means
Browsers do not have a JVM, so the GWT compiler cannot translate calls into rt.jar/the platform modules.
Instead it needs the source of every class that client code touches.
For your own classes that source is on the module’s source path; for java.lang.String, java.util.ArrayList and the rest of the JRE it is the JRE emulation library: hand-written, translatable Java source files, shipped inside gwt-user-2.13.1.jar under com/google/gwt/emul/ and registered by the module com.google.gwt.emul.Emulation. A comment in that file tells you not to inherit it directly but to inherit com.google.gwt.core.Core, which every normal GWT module does through com.google.gwt.user.User.
The module consists of a <super-source/> element: the files below com/google/gwt/emul are re-rooted so that com/google/gwt/emul/java/util/ArrayList.java is seen by the compiler as java/util/ArrayList.java.
com.example.bookshelf.client / .shared"] --> jdt["GWT compiler front end
(JDT, -sourceLevel 17)"] emul["gwt-user: com/google/gwt/emul
String, ArrayList, Optional, streams ..."] --> jdt jdt --> check{"Does every referenced
type have source?"} check -- "no" --> err["No source code is available for type ...
did you forget to inherit a required module?"] check -- "yes" --> js["Optimised JavaScript
permutation .cache.js"] jvm["Server JVM: real java.*"] -. "never used for the browser build" .-> js
Source: GWT 2.13.1 — Emulation.gwt.xml
Consequences worth keeping in mind:
-
The emulated classes are subsets with their own behaviour. A method that exists on the JVM may be absent, and a method that exists may behave differently in details (examples below).
-
Only the types and members that your code actually reaches survive into the output (dead-code elimination), so referencing a heavy class costs code size, not just correctness. Compile Reports & Optimization and Code Splitting cover how to measure it.
-
A class that has no emulation is not "slow" or "unsupported at runtime": it simply does not compile. The error appears at compile time, which is why the Developer Guide advises running early and often.
-
GWT also ships a few similar classes under other names where emulating the real class would be too expensive; the Developer Guide lists
com.google.gwt.i18n.client.DateTimeFormat,com.google.gwt.i18n.client.NumberFormat,com.google.gwt.user.client.rpc.IsSerializableandcom.google.gwt.user.client.Timer.
What the library provides
The reference page RefJreEmulation.html lists the supported packages, and for each type the supported constructors, methods and fields.
It is generated from the emulation sources, so it follows the release (2.13.0 reworked it to also document what is not supported, below).
The packages it covers, and what the 2.13.1 sources contain, are:
| Package | What is there (not exhaustive) |
|---|---|
|
|
|
the annotation meta types; from |
|
the collection hierarchy ( |
|
the functional interfaces; |
|
|
|
|
|
|
|
byte and char stream base classes ( |
|
|
Source: GWT — JRE Emulation
The headline point is that the library is a deliberately small, browser-oriented subset: several packages are present only with a handful of types.
Absent entirely, and therefore compile errors, are among others java.time, java.util.regex, java.util.UUID, java.io.File, java.lang.Thread, java.util.Scanner and java.net.
The sample code below compiles a class that uses several of them and shows the messages the compiler produces.
package com.example.bad.client;
import com.google.gwt.core.client.EntryPoint;
import java.time.LocalDate;
import java.util.UUID;
import java.util.regex.Pattern;
public class BadEntry implements EntryPoint {
public void onModuleLoad() {
LocalDate d = LocalDate.now();
UUID id = UUID.randomUUID();
Pattern p = Pattern.compile("a+");
try { Thread.sleep(10); } catch (InterruptedException e) { }
new java.io.File("books.txt").exists();
}
}
Compiling it with java -cp ... com.google.gwt.dev.Compiler -sourceLevel 17 -strict com.example.bad.Bad fails with one error per unsupported type (the compiler repeats the list in a second "Finding entry point classes" pass):
[ERROR] Errors in 'file:/.../com/example/bad/client/BadEntry.java'
[ERROR] Line 10: No source code is available for type java.time.LocalDate; did you forget to inherit a required module?
[ERROR] Line 12: No source code is available for type java.util.regex.Pattern; did you forget to inherit a required module?
[ERROR] Line 11: No source code is available for type java.util.UUID; did you forget to inherit a required module?
[ERROR] Line 14: No source code is available for type java.io.File; did you forget to inherit a required module?
[ERROR] Line 13: No source code is available for type java.lang.Thread; did you forget to inherit a required module?
[ERROR] Hint: Check the inheritance chain from your module; it may not be inheriting a required module or a module may not be adding its source path entries properly
Source: GWT — FAQ: Troubleshooting
The hint is misleading for JRE types: no module can be inherited to obtain java.time.LocalDate; either avoid it, use a GWT replacement, or supply the class yourself with super-source (below).
A class that is emulated but lacks a member produces a different message, because the type resolves and only the method does not:
[ERROR] Line 13: The method format(String, int) is undefined for the type String
[ERROR] Line 16: The method forName(String) is undefined for the type Class
Because some errors surface only in the second phase, read the whole list before fixing one by one, and compile with -strict (alias -failOnError, which the archetype turns on through <failOnError>true</failOnError>) so that a unit with errors is never silently skipped.
What is missing, and why
The page RefJreMissingEmulation.html (introduced with 2.13.0, whose notes describe it as improved tracking that documents unsupported APIs with links to issues) lists, per package, the methods, fields and inner classes that exist in the JDK but not in GWT, each linked to the GitHub issue that tracks the work or explains why it will not happen.
It does not list top-level classes that are not emulated at all.
The groups that matter most in practice are:
| Tracking issue | Theme | Typical missing members |
|---|---|---|
gwtproject/gwt#9630 |
Reflection |
|
gwtproject/gwt#3946 |
Localization |
|
gwtproject/gwt#10211, #10212, #10213 |
System resources and properties |
|
gwtproject/gwt#10221 |
Miscellaneous Java 17 omissions |
|
gwtproject/gwt#9643, #9909 |
Unsigned arithmetic and newer |
|
gwtproject/gwt#1989 |
Unicode |
code-point overloads of |
gwtproject/gwt#10216 |
Raw bits |
|
gwtproject/gwt#10217, #10218, #611 |
Concurrent APIs, |
|
Two practical rules follow.
First, check RefJreMissingEmulation.html before adding a JDK 9-17 convenience method to shared code: a method that is new since Java 8 may exist (2.13.0 added Java 9+ methods to Integer, Long, Math, String, Objects, Collectors, Stream, IntStream and others) or may be tracked as missing.
Second, anything in the "Reflection" group will not come back as such: GWT compiles to one script, can load code only through code splitting, and has no runtime class metadata beyond names. Use deferred binding, generators or annotation processors to produce what reflection would have found, as covered on Deferred Binding & Generators.
For dates and times the practical answer is java.util.Date plus com.google.gwt.i18n.client.DateTimeFormat (and the org.gwtproject.* or third-party java.time ports described on Ecosystem & Libraries) rather than java.time; for text formatting use NumberFormat, DateTimeFormat and Messages interfaces from Internationalization rather than String.format.
The Java side of the same topics is on the Dates and Times, Strings and Text and Annotations and Reflection pages.
Supported Java language levels and features
The language level (syntax) and the library level (emulation) move independently: the compiler’s front end (JDT) accepts the syntax, while the library has to contain the classes the syntax relies on. The release notes give the history:
| GWT | Language and library support |
|---|---|
Before 2.8.0 |
Java 7 or lower syntax; no lambdas |
2.8.0 (2016) |
Source level Java 8: lambdas, method references, default and static interface methods; "partial support for Java 8 standard library APIs" ( |
2.9.0 |
Adds language levels 9, 10 and 11; tooling (compiler, server code) is tested on Java 8, 11 and 14 |
2.10.0 / 2.11.0 |
Library level raised to Java 11 for collections, streams and more ( |
2.12.0 |
Syntax of Java 12 to 17: text blocks, |
2.13.0 / 2.13.1 |
|
Planned |
The roadmap states the goal of raising both the supported language features and the JRE emulation to Java 21; Java 18-21 syntax (for example pattern matching in |
Source: GWT — Release notes: 2.13.0, GWT — Roadmap
The compiler flag is -sourceLevel (values auto, 1.8, 9, 10, 11, 17 in the 2.13.1 help text).
The gwt-maven-plugin exposes it as <sourceLevel>, whose default is the user property maven.compiler.source; the archetype-generated parent POM sets it explicitly next to failOnError:
<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>
The command-line compiler’s default is 1.8 (the default argument registered in ToolBase; the -help text and the Developer Guide are right), so Java 12 to 17 syntax needs an explicit -sourceLevel 17. The value auto resolves to the running JVM’s level.
Set it explicitly in the build, and keep the release setting of maven-compiler-plugin and the GWT sourceLevel equal so javac (server and tests) and the GWT front end accept the same code.
The following shared class uses Java 17 syntax and compiled and ran in the browser build and on the JVM:
package com.example.probe.shared;
public final class Pricing {
private Pricing() {}
public static String label(Object o) {
if (o instanceof Integer i && i > 10) { // instanceof pattern (2.12.0)
return "many:" + i;
}
String kind = o == null ? "null" : o.getClass().getSimpleName();
return switch (kind) { // switch expression (2.12.0)
case "null" -> "no value";
case "String" -> "text";
default -> "other:" + kind;
};
}
}
Source: GWT — Release notes: 2.12.0
Records (record Copy(String title, int n) {}), text blocks, var, and sealed hierarchies with permits also compiled at -sourceLevel 17; in the browser a record printed as Copy[title=Emma, n=2] and its generated equals returned true, as on the JVM.
What does not compile is Java 21 pattern matching in switch. With level 17 the compiler reports:
[ERROR] Line 18: The preview feature Pattern Matching in Switch is only available with source level 19 and above
[ERROR] Line 17: Cannot switch on a value of type Object. Only convertible int values, strings or enum variables are permitted
Use instanceof patterns and if/else chains until the supported level moves.
The syntax itself is the ordinary Java covered in the Records and Sealed Classes, Pattern Matching, Lambdas and Method References, Generics and Streams and Collectors pages; the point here is only whether the GWT version and its library accept it.
Two details are specific to GWT:
-
Generators cannot see Java 8
defaultandstaticinterface methods; this is why the notes recommend annotation processors over generators for new code. -
Anything generated at run time by libraries that depend on reflection (annotation-driven dependency injection, serialisation by reflection, Jackson without a GWT adapter) has no equivalent; the replacements are covered on Ecosystem & Libraries.
Language semantics that differ in the browser
The Developer Guide’s "Language support" section lists the places where the JavaScript target leaks through. Checked against 2.13.1:
-
Exceptions the JVM raises implicitly (
NullPointerException,StackOverflowError,OutOfMemoryError) do not occur as such. Dereferencingnullthrewcom.google.gwt.core.client.JavaScriptException, so nevercatch (NullPointerException e)to detect a null. A bad cast threwjava.lang.ClassCastException, as expected. -
Array indexes and integer division by zero are not checked. Writing
arr[2]of a one-elementint[]raised nothing, and1 / 0onintproduced0rather thanArithmeticException. -
Assertions are removed unless the compiler runs with
-checkAssertions(the Developer Guide still names-ea). -
Threads:
synchronizedis accepted and ignored;Object.wait/notify/notifyAllmake the compile fail;Threaditself does not exist. Asynchrony is callbacks,SchedulerandTimer. -
Reflection:
Object.getClass().getName()andgetSimpleName()work (com.example.probe.client.ProbeEntry$CopyandCopywere printed), but not much more.-XclassMetadata/-XnoclassMetadatacontrols whether those names are kept. -
Finalizers,
strictfpandThrowable.getStackTrace()have no meaningful implementation in production mode.
Numbers: int, long, float and double
JavaScript has one numeric type, the 64-bit floating point number.
GWT builds the Java primitives on top of it, and the differences are the main source of bugs in code that was written for the JVM and is later shared.
The probe below runs the same statements in the browser (2.13.1, production-style output) and on JDK 21.
The operands come from Integer.parseInt/Long.parseLong on purpose: the compiler folds constant expressions (Integer.MAX_VALUE + 1 printed -2147483648 in the browser only because it was folded at compile time).
int r1 = Integer.parseInt("2147483647");
int r2 = Integer.parseInt("1");
p("runtime MAX + 1", r1 + r2);
p("runtime MAX + 1 (|0)", (r1 + r2) | 0);
p("runtime 46341*46341", Integer.parseInt("46341") * Integer.parseInt("46341"));
p("runtime 1 << 33", r2 << Integer.parseInt("33"));
p("runtime byte 127+1", (byte) (Byte.parseByte("127") + r2));
p("runtime long MAX+1", Long.parseLong("9223372036854775807") + Long.parseLong("1"));
p("runtime long mul", Long.parseLong("4294967296") * Long.parseLong("4294967296"));
p("runtime (int) long", (int) Long.parseLong("4294967297"));
| Expression | Browser | JVM | Reading |
|---|---|---|---|
|
2147483648 |
-2147483648 |
|
|
-2147483648 |
-2147483648 |
the |
|
2147488281 |
-2147479015 |
|
|
2 |
2 |
shifts are masked to 5 bits as in Java |
|
-128 |
-128 |
narrowing casts do wrap |
|
-9223372036854775808 |
-9223372036854775808 |
|
|
0 |
0 |
ditto |
|
1 |
1 |
|
|
6.851451984453826e+40 |
-817261598 |
unwrapped |
The rules that explain the table:
-
byte,short,charandintare doubles. Overflow ofintarithmetic is not wrapped (the Developer Guide says the same about overflowing integral types). The cure for code that depends on 32-bit wrapping (hashes, checksums, random number generators,Math.abs(Integer.MIN_VALUE), which printed 2147483648) is an explicit| 0or the library methods, which already contain it;String.hashCode()on the browser agreed with the JVM ("title".hashCode()is 110371416 on both). Division is correct:5 / 2is 2 and-5 / 2is -2,-7 % 3is -1. -
longis emulated and is correct over the full 64-bit range. In 2.13.1 the representation is hybrid (com.google.gwt.lang.LongLib): values whose magnitude is below 2^44 are held as a plain JavaScript number and operated on withdoublearithmetic, and larger values are held as three chunks of 22, 22 and 20 bits (thel,m,hfields visible in the generated code) with slower arithmetic. Counting loops over ids and timestamps mostly stay in the cheap representation; heavy 64-bit bit manipulation does not. The generated function forPricing.centsis the callmul_0(units, fromInt_0(unitCents)), not a native*. -
longand JavaScript do not mix freely. The Developer Guide notes thatlongcannot be used in JSNI because it is not a native JavaScript number; keep exported or native signatures toint,doubleorStringand see JsInterop for the rules. -
floatis adouble.0.1f + 0.2fprinted0.30000000447034836in the browser while the JVM prints0.3: the extra precision is visible whenever afloatis converted to text or compared exactly. Usedouble(orBigDecimal) deliberately in shared code. -
doubleformatting and hashing differ in places.Double.toString(1e21)printed1.0E21andDouble.toString(100.0)printed100.0, as in Java, butDouble.valueOf(1.5).hashCode()is1in the browser (the emulation returns(int) d) against1073217536on the JVM. Do not persist or compare hash codes of doubles across client and server.Long.hashCodematched the JVM (705032705 for5000000000L). -
Casting
(int) 3.9e10saturates to2147483647, as in Java;Math.round(-2.5)gave-2, as in Java.
These semantics are why the Bookshelf Book of the scenario keeps a long id and an int copies and does no arithmetic on them beyond counting and comparing; a checksum (an ISBN check digit, a cache key) belongs in a method with explicit | 0 or in a long.
The Java-side background is on the Primitive Types and Variables, Operators and Expressions and Numbers and Math pages.
Runtime checks you can tune: jre.checks
Separately from language semantics, the library contains defensive checks (index bounds in ArrayList, iterator state, argument and type checks) controlled by configuration properties defined in Preconditions.gwt.xml: jre.checks.checkLevel (NORMAL, OPTIMIZED or MINIMAL, default NORMAL) and the finer jre.checks.bounds, jre.checks.api, jre.checks.numeric and jre.checks.type (each AUTO, ENABLED or DISABLED), plus jre.checkedMode.
The compiler flag -XcheckCasts is marked deprecated in favour of jre.checks.checkLevel.
Compiling the probe with -setProperty jre.checks.checkLevel=MINIMAL changed this line:
NORMAL : ArrayList.get(3) throws = java.lang.IndexOutOfBoundsException: Index: 3, Size: 0
MINIMAL : new ArrayList().get(3) = undefined
while Integer.parseInt("x") still threw NumberFormatException: For input string: "x" and List.of(1).add(2) still threw UnsupportedOperationException.
The whole probe’s single permutation file went from 37,223 bytes (NORMAL) to 32,343 bytes (MINIMAL) with -style OBF.
Lower levels trade diagnostics for size; use them only for a release build that is covered by tests at NORMAL, and never to hide a bug: with checks off the code above does not fail, it silently returns undefined.
Strings and regular expressions
String is the JavaScript string, and the emulation re-implements the Java methods on top of it.
Most methods behave as in Java (substring, indexOf, replace, trim, toUpperCase gave STRASSE for "stra\u00dfe" as on the JVM); the differences are concentrated in four places.
Regular expressions are JavaScript regular expressions. String.matches, replaceAll, replaceFirst and split hand the pattern to the browser’s RegExp. The Javadoc comment of the emulated split says the same and advises using only the subset of regular expression syntax common to Java and JavaScript. Observed with Chrome:
| Construct | Browser result | JVM result |
|---|---|---|
|
|
the same |
|
|
the same |
|
|
|
|
|
|
|
|
|
|
|
The dangerous cases are the silent ones (\p{Alpha}, \Q..\E, character-class intersections [a-z&&[^bc]], lookbehind support that depends on the browser), which return a wrong answer rather than failing.
Keep regular expressions in shared code to the common subset, put them in one class, and test that class both on the JVM and with a GWTTestCase (see Testing).
The Java-side syntax is explained on the Regular Expressions page.
The portable alternative is com.google.gwt.regexp.shared.RegExp. It is in the module com.google.gwt.regexp.RegExp, has a JavaScript-style API (compile(pattern), compile(pattern, flags), exec, test, replace, split, quote, getGlobal, …) and exists in two implementations: on the browser it calls the native RegExp; on the JVM it is backed by java.util.regex.Pattern, and it translates the JavaScript replacement syntax ($&, $$, backslashes) for Java. Its Javadoc warns that Java-only constructs such as [a-z&&[^bc]], (?<=foo), \A and \Q work only in the JVM implementation and are rejected by neither. The shared validation class used in the Bookshelf (for the ISBN field of the book editor) is therefore:
package com.example.probe.shared;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
/** Shared validation: runs unchanged in the browser and on the server. */
public final class Isbn {
private static final RegExp ISBN13 =
RegExp.compile("^(97[89])-(\\d{1,5})-(\\d{1,7})-(\\d{1,7})-(\\d)$");
private Isbn() {}
public static String describe(String text) {
MatchResult m = ISBN13.exec(text);
return m == null ? "invalid" : "prefix " + m.getGroup(1) + ", check digit " + m.getGroup(5);
}
public static String hyphenated(String compact) {
return RegExp.compile("(\\d{3})(\\d)(\\d{4})(\\d{4})(\\d)")
.replace(compact, "$1-$2-$3-$4-$5");
}
}
Source: GWT Javadoc — RegExp
Both sides printed prefix 978, check digit 7, invalid and 978-0-5965-2068-7 for the inputs 978-0-596-52068-7, 12-34 and 9780596520687.
For the module, add <inherits name="com.google.gwt.regexp.RegExp"/>; on the server the classes come from the GWT library jar (they are in gwt-servlet-jakarta-2.13.1.jar as well as in gwt-user).
String.split follows Java 8 semantics since 2.13.0. The release notes list it as a breaking change. The implementation now discards a zero-length match at the start of the input, and trailing empty strings are removed unless the limit is negative. Observed on 2.13.1 and identical on the JVM:
"a,b,,".split(",") = [a, b]
",a,b".split(",") = [, a, b]
"abc".split("") = [a, b, c]
"abc".split("", -1) = [a, b, c, ]
"".split(",") = [] (length 1: the single empty string)
Source: GWT — Release notes: 2.13.0
Code written against earlier GWT versions that relied on a leading empty element from a zero-width split must be adjusted when upgrading.
The same release makes Collectors.toMap throw when a value is null (the JVM behaviour, although not what its Javadoc says) and removes the AccessControlException emulation.
Characters and locales are ASCII-oriented. The Character emulation says several methods "do not properly handle characters outside of ASCII": isLetter, isDigit, isLetterOrDigit, isLowerCase, isUpperCase, and Character.isLetter('\u00e9') returned false in the browser (it is true on the JVM). Use RegExp with the right flags, or compare against explicit ranges, for user-facing text such as the author names of the Bookshelf, and use the i18n classes for case and number formatting.
Formatting is not String.format. String.format, formatted and printf are missing (the localization issue above); use NumberFormat, DateTimeFormat, Messages interfaces and SafeHtml templates.
Writing shared code
Shared code is Java that must compile twice: with javac against the full JDK for the server, and with the GWT compiler against the emulation for the browser.
The Bookshelf keeps it in its own Maven module, bookshelf-shared, whose jar is also published with the sources classifier because the GWT compiler needs source files:
<!-- bookshelf-shared/pom.xml (excerpt) -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- bookshelf-client/src/main/module.gwt.xml (excerpt) -->
<module>
<inherits name="com.google.gwt.user.User" />
<source path="client" />
<source path="shared" />
<entry-point class="com.example.bookshelf.client.App" />
</module>
The module file lives in the package com.example.bookshelf, so <source path="shared"/> means com.example.bookshelf.shared, found in the sources jar of bookshelf-shared.
If that jar was not built, the client compile fails, for example when running mvn test -pl bookshelf-client -am without package:
No source code is available for type com.example.bookshelf.shared.Book; did you forget to inherit a required module?
Use mvn verify (or install) so the sources jar exists before the client is compiled.
The discipline for shared classes is short:
-
Only use what both sides have. Restrict shared classes to the emulated subset: no reflection,
java.time,java.util.regex,String.format, threads, files orjavax/jakartaclasses. A server import in a shared file is a client compile error, and the other way round the server may use the full JDK only in server-only classes. -
No dependence on the numeric and string deviations above. Hash codes of
double, unwrappedintarithmetic, regex flavours andfloattext must not be part of a contract between client and server. -
Serialisation types are shared types. Classes sent through GWT-RPC (such as
BookandBookPage) implementjava.io.SerializableorIsSerializable, have a default constructor and use only serialisable field types; that rule belongs to GWT-RPC. -
Keep a JVM-only escape hatch inside the class rather than in a copy. Two mechanisms exist:
@GwtIncompatible, andGWT.isClient()/GWT.isScript()checks.
@GwtIncompatible (com.google.gwt.core.shared.GwtIncompatible; a copy under any other package works because only the simple name matters) removes the annotated type, method, constructor or field from what the GWT compiler sees, while javac still compiles it for the server.
Its Javadoc warns that removing members this way can have surprising effects together with method overloading or inheritance. The probe’s Pricing.today() uses java.time and was ignored by the browser build, which compiled without error:
import com.google.gwt.core.shared.GwtIncompatible;
public final class Pricing {
@GwtIncompatible("uses java.time, which is not emulated")
public static String today() {
return java.time.LocalDate.now().toString();
}
}
Source: GWT Javadoc — GwtIncompatible
When the behaviour differs but both versions must exist in the same compilation unit, ask the environment. com.google.gwt.core.shared.GWT is the class made for shared code; it also exists on the JVM, where isClient(), isScript() and isProdMode() return false and getVersion() returns null:
import com.google.gwt.core.shared.GWT;
public final class Environment {
public static String describe() {
return GWT.isScript() ? "browser, compiled JavaScript"
: GWT.isClient() ? "browser, Dev Mode or test"
: "plain JVM (server or JUnit)";
}
}
Running the shared GWT calls in a plain main on JDK 21 printed false false false null for isClient, isScript, isProdMode and getVersion, and GWT.create(Runnable.class) threw:
java.lang.UnsupportedOperationException: ERROR: GWT.create() is only usable in client code! It cannot be called, for example, from server code. If you are running a unit test, check that your test case extends GWTTestCase and that GWT.create() is not called from within an initializer or constructor.
Prefer @GwtIncompatible or super-source over runtime branching for anything that pulls in a non-emulated class: a GWT.isClient() branch still has to compile on both sides, whereas the @GwtIncompatible member is not seen by GWT at all, and the optimiser removes unreachable branches of isScript() checks only after the types in them have been resolved.
super-source: supplying your own emulation
The <super-source> module element "re-roots" a source path, so that files below it replace or add classes as if the directory prefix were not there (the module XML elements are covered in Project Structure & Modules).
The Developer Guide’s own example is adding java.util.UUID: put the file in com/example/myproject/jre/java/util/UUID.java, add <super-source path="jre"/> to MyProject.gwt.xml, and the compiler sees it as java/util/UUID.java. GWT uses the same technique for its own emulation.
The same mechanism lets shared code have a different implementation in the browser than on the server without changing the class’s name or callers:
<!-- com/example/probe/Probe.gwt.xml -->
<module rename-to="probe">
<inherits name="com.google.gwt.user.User"/>
<source path="client"/>
<source path="shared"/>
<super-source path="super"/>
<entry-point class="com.example.probe.client.ProbeEntry"/>
</module>
// com/example/probe/shared/Platform.java (used by javac for the server)
package com.example.probe.shared;
public class Platform {
public static String describe() {
return "JVM: " + System.getProperty("java.version");
}
}
// com/example/probe/super/com/example/probe/shared/Platform.java (seen by the GWT compiler instead)
package com.example.probe.shared;
import com.google.gwt.core.client.GWT;
public class Platform {
public static String describe() {
return "Browser: " + GWT.getModuleName();
}
}
In the browser Platform.describe() returned Browser: probe; on the JVM the same class name returned the JVM: ... text.
The two files have the same fully qualified name com.example.probe.shared.Platform, so callers (here ProbeEntry) and the shared module need no change.
This is exactly how com.google.gwt.regexp.shared.RegExp is built: a pure Java version in the shared path for the server, a super-sourced version that calls native JavaScript for the browser (its module declares both <source path="shared"/> and <super-source path="super"/>).
Rules and caveats:
-
The path is relative to the module file and the whole subtree is re-rooted, so the package directories must be repeated below it (
super/com/example/probe/shared/). -
If both a normal source and a super-source define the same class, the super-source wins for the GWT compiler.
javacmust not compile the super-source files (they would clash with the real class), so keep them outside the Java source directories: the default layout ofgwt-maven-pluginreservessrc/main/super/(andsrc/test/super/) for them, used by the goalsgwt:add-super-sourcesandgwt:add-test-super-sources, and the files have to end up in the sources jar so the client build can see them. -
Use it to provide a class that GWT lacks (a minimal
UUID, aPatternfacade), or to give shared classes a browser-specific body. Do not use it to patch GWT’s own emulation of a class that exists unless you accept maintaining it across releases. -
In classic Dev Mode (history, removed from the workflow of the Super Dev Mode era) the native JRE classes always superseded source for
java.*, so such overrides were never exercised there. Super Dev Mode compiles to JavaScript and does use them, so a test that runs in a browser or withGWTTestCaseis the check. -
The mechanism is GWT-specific; J2CL uses a different convention for native overrides (see J2CL & the Future), so code that depends on super-source needs a second implementation when moving to J2CL.
The GWT class API
The "GWT Class API" reference page, RefGWTClassAPI.html, is a short pointer: it only introduces the class library (user interface, RPC, internationalisation, unit testing) and links to the Javadoc; the Javadoc is the actual reference.
The class that every application meets first is com.google.gwt.core.client.GWT; com.google.gwt.core.shared.GWT is its JVM-safe base for shared code.
The members below were checked in the 2.13.1 sources and, where marked, run:
| Member | Behaviour |
|---|---|
|
Deferred binding: the argument must be a class literal, replaced at compile time by |
|
|
|
|
|
|
|
the running module’s (renamed) name; the probe printed |
|
the URL prefix for module-relative resources such as RPC entry points; guaranteed to end with |
|
prefix for static files produced by the compiler; normally equal to the module base, different when static resources are served from another server |
|
the URL prefix of the host page ( |
|
the permutation’s hash (32 hexadecimal characters in the probe); |
|
the GWT version ( |
|
to the Dev Mode shell, or to the browser console in Super Dev Mode; calls are optimised out of production output |
|
the handler for exceptions about to escape to the browser from event callbacks, timers and RPC. |
|
code-splitting split point; in plain bytecode it simply schedules the callback. Covered with Code Splitting |
|
emits a JavaScript |
|
|
|
deprecated; use |
|
nested interface, |
The Bookshelf entry point uses several of them in a few lines:
public class App implements EntryPoint {
@Override
public void onModuleLoad() {
GWT.setUncaughtExceptionHandler(e -> {
GWT.log("Uncaught", e); // console in Super Dev Mode, removed in production
Window.alert("Something went wrong; please reload the page.");
});
// Defer the rest so that exceptions in it reach the handler.
Scheduler.get().scheduleDeferred(() -> {
CatalogServiceAsync catalog = GWT.create(CatalogService.class);
((ServiceDefTarget) catalog).setServiceEntryPoint(GWT.getModuleBaseURL() + "catalog");
if (!GWT.isProdMode()) {
GWT.log("Running " + GWT.getModuleName() + " on GWT " + GWT.getVersion());
}
});
}
}
Source: GWT — Compatibility
@RemoteServiceRelativePath removes the need for setServiceEntryPoint in almost every case; the explicit form is shown because it is where getModuleBaseURL() is used.
A user-facing message in the handler must not include the exception text, which may contain internal details.
Outdated material in older books and posts
Older GWT material, including the first-edition chapters on the generated JavaScript and the JRE emulation library (GWT 1.3 and 1.4, 2007), describes a world with these differences:
| Older statement | Today |
|---|---|
Client code must be written to Java 1.4: no generics, enums, annotations or varargs; set the IDE to 1.4 compatibility |
Java 8 syntax since 2.8, Java 17 syntax since 2.12 ( |
|
|
The emulation library covers |
it now includes streams, |
The reference to check supported classes is a single list |
two pages: |
JUnit 3 |
JUnit 4 |
Source: Manning — GWT in Action
Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
|
The class is not in the emulation |
Use |
|
Member missing from an emulated class (localization, issue #3946) |
|
|
Java regex syntax passed to the JavaScript engine |
Use the common subset, |
|
|
Do not read JVM properties on the client; define a configuration property in the module |
|
Java 21 syntax at |
|
|
The |
|
Hash, checksum or random generator gives different values on client and server |
Unwrapped |
|
|
A null dereference becomes |
Check for |
A |
Java-only regex constructs |
Keep shared patterns in the common subset and test in both runtimes |
Code works with checks and returns |
|
Test at |
Source: GWT — FAQ: Troubleshooting
What changed recently
-
2.8.0: Java 8 syntax and the first Java 8 library APIs; classic Dev Mode deprecated; JSNI methods may no longer reference methods of
Double,StringandBoolean. -
2.9.0: language levels 9 to 11; Elemental deprecated in favour of Elemental2.
-
2.11.0: library at Java 11 level for collections, streams and more; Jakarta artifacts for GWT-RPC and RequestFactory.
-
2.12.0: Java 12-17 syntax,
-XfragmentMergewithout effect (its handler was removed in 2.13.0),Normalizer, Java 11Stringmethods. -
2.13.0:
String.splitJava 8 semantics,Collectors.toMapthrows onnullvalues,AccessControlExceptionemulation removed, Java 9+ methods inInteger,Long,Math,StrictMath,String,BigInteger,BitSet,Objects, streams andCollectors, and the new missing-emulation reference page. -
2.13.1: record
equalscompares fields withObjects.equalssonullcomponents work.
Source: GWT — Release notes
What’s Changed & Migration lists the changes across the whole section.