Deferred Binding & Generators
|
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, JDK 21 with -sourceLevel 17 and the
Jakarta Servlet 6 stack of the modular-webapp archetype, checked on 2026-10-08. Deferred binding spans three kinds of
code. The GWT.create() call and the classes it selects are client (and shared) code that is compiled to
JavaScript. The module XML rules and properties are build configuration read by the compiler. A generator is a
plain Java class that runs inside the compiler’s JVM, so it is tooling code and never reaches the browser. Every
example below was compiled and run on the Bookshelf scenario of this section with JDK 21; the logs, generated sources and
browser results are the real ones.
Why deferred binding exists
GWT compiles to JavaScript ahead of time and has no reflection. Deferred binding is the replacement: the compiler decides,
at compile time, which implementation class a GWT.create(Type.class) call stands for, and it makes that decision once
per permutation (one JavaScript program per combination of property values, see
Compiler & Compilation). The developer guide presents it as GWT’s counterpart
of Java reflection. It has three consequences that shape everything else on this page.
-
Smaller downloads. A permutation contains only the chosen implementation, so a browser or locale never downloads code for the others. In the Bookshelf run below, the
.cache.jsfile of themobilepermutation contains the stringmobileonce anddesktopnot at all, and thedesktopfile the reverse. -
No run-time lookup. The answer is wired in during compilation, so there is no registry, map or virtual dispatch to pay for at run time.
-
Less reachable code is compiled. Only types that the chosen answers reach are compiled; every other candidate is pruned like any other unused code.
The price is permutations: each binding property multiplies the number of JavaScript programs (see the permutation counts table), and every generator or replacement rule is evaluated for each one.
GWT uses the mechanism all over its own library. The developer guide notes that many widgets, panels and the DOM class
use it invisibly, while features such as GWT-RPC and Internationalization make the application developer call
GWT.create() explicitly. The rules below come from the 2.13.1 module files.
You call GWT.create() on |
Module file | Rule |
|---|---|---|
a |
|
|
a |
|
|
a |
|
|
a |
|
|
a |
|
|
a |
|
|
a |
|
|
an |
|
a generator each |
a |
|
|
the |
|
|
the |
|
|
The pages that use these rules are UiBinder, ClientBundle & Images, SafeHtml & XSS, MVP, Activities & Places, Editors, Validation and RequestFactory & AutoBeans.
GWT.create() and the rebind process
GWT.create(Class<?>) returns an instance of "the class the rules say this type stands for". Its Javadoc pins down the
one rule that you control: the argument must be a class literal, because the production compiler must know the type
statically. The same method exists in com.google.gwt.core.shared.GWT for code that is shared with the server. Server code that
needs a working create installs the registry-based ServerGwtBridge (ServerGwtBridge.getInstance() registers
itself); when no bridge is installed, as in a plain JUnit test, the call throws UnsupportedOperationException with
the message ERROR: GWT.create() is only usable in client code! followed by advice to extend GWTTestCase.
// Client code: the argument is always a class literal
PageSizer sizer = GWT.create(PageSizer.class); // answer chosen per permutation
BookDescriber describer = GWT.create(BookDescriber.class); // answer generated by a Generator
// Not allowed: the compiler cannot resolve a variable
Class<PageSizer> type = PageSizer.class;
PageSizer broken = GWT.create(type); // error: Only class literals may be used as arguments to GWT.create()
How the compiler decides
For every GWT.create(T.class) it can reach, and for every permutation, the compiler asks a StandardRebindOracle for
the answer. The oracle walks the module’s rules, replace-with, generate-with and fail, and takes the first rule
whose conditions are all true. Rules are kept in a deque to which each new rule is added at the front, so the rule
declared last is examined first. Inherited modules are loaded where their inherits element appears, so a rule in the
inheriting module’s body, after the inherits, overrides the same rule of the module it inherits. A comment in GWT’s
own Resources.gwt.xml makes the same point: the last matching rule wins, so a later rule selectively overrides an
earlier one.
asked once per permutation"] --> next{"Another rule?
newest rule first"} next -->|yes| cond{"All conditions true?
when-type-is, when-type-assignable,
when-property-is, all, any, none"} cond -->|no| near["Remember it if a fallback-value
could make it match"] near --> next cond -->|yes| kind{"Kind of rule"} kind -->|replace-with| rep["Answer = the named class"] kind -->|generate-with| gen["Run the Generator
answer = the generated class"] kind -->|fail| fail["Error: Deferred binding
request failed for type"] next -->|no more rules| closest{"Closest fallback
match remembered?"} closest -->|yes| use["Use that rule
INFO message about the closest rule"] closest -->|no| self["Answer = T itself"] use --> kind rep --> check{"Answer is a concrete class
with a default constructor?"} gen --> check self --> check check -->|no| err["Compile error on the GWT.create line"] check -->|yes| rec["Record the answer for this permutation
compile only what it reaches"]
The figure shows the decision as the 2.13.1 StandardRebindOracle implements it. If no rule matches at all, the answer is
the requested type itself, so GWT.create(DesktopPageSizer.class) on a concrete class with a public no-argument
constructor works without any rule. Interfaces and abstract classes need a rule.
The answer is then validated, and the failures are reported against the line of the GWT.create() call. The rules and
messages below were all reproduced:
-
The answer must be a class:
Rebind result 'com.example.bookshelf.demo.client.PageSizer' must be a class. -
It must not be abstract:
Rebind result '...AbstractPageSizer' cannot be abstract. -
It must have a default constructor:
Rebind result '...LockedPageSizer' has no default (zero argument) constructors. -
It is not checked against the requested type at compile time. A
replace-withclass that does not implement the requested interface compiles and then fails in the browser withUncaught Error: java.lang.ClassCastException.
The result of the decision is visible in compilation-mappings.txt in the module’s output folder, which lists the
property values that each .cache.js permutation file was compiled for. For the Bookshelf demo module of this page:
247CFD21DB7DBAF36E5B92059CD499A7.cache.js
formfactor mobile
user.agent safari
88FA6B8019A2BD7EA32E581674B41B1C.cache.js
formfactor desktop
user.agent safari
93278AB0B7C10801D0890D86D04BEB23.cache.js
formfactor mobile
user.agent gecko1_8
BBC8E480FB3B9DFA6F1A6F9CA0996199.cache.js
formfactor desktop
user.agent gecko1_8
At run time
Nothing is decided in the browser except the selection of the permutation. The bootstrap script
(<module>.nocache.js) runs the property providers, looks the resulting values up in its permutation table and loads
the matching .cache.js. The GWT.create() calls inside that file were already replaced by new expressions of the
chosen classes, which is why the single-line GWT.create in the Java source costs nothing at run time. See
Linkers & Bootstrap for the script itself; the property part is in Property providers.
Replacing a type: replace-with
<replace-with class="..."> names the class that stands in for the requested type. Its children are the conditions that
decide when it applies. The developer guide’s own example is PopupImpl: a default implementation, plus an
override for some browsers. The current Popup.gwt.xml of 2.13.1 is much shorter than the guide’s sample because the
only remaining user.agent values are gecko1_8 and safari:
<module>
<inherits name="com.google.gwt.core.Core"/>
<inherits name="com.google.gwt.user.UserAgent"/>
<!-- Mozilla needs a different implementation due to issue #410 -->
<replace-with class="com.google.gwt.user.client.ui.impl.PopupImplMozilla">
<when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
<when-property-is name="user.agent" value="gecko1_8"/>
</replace-with>
</module>
Source: GWT — Deferred Binding: Deferred Binding Using Replacement, GWT source (2.13.1) — Popup.gwt.xml
PopupPanel itself holds private static final PopupImpl impl = GWT.create(PopupImpl.class); and calls
impl.setVisible(...) where the behaviour differs. There is no rule for plain PopupImpl, so the default is the class
itself and it must be concrete. The pattern is the standard recipe for "one API, several implementations":
-
an interface or base class that application code uses (the requested type)
-
a default implementation and one subclass or sibling per variant
-
one
replace-withper variant, each with the property it depends on -
a single
GWT.create(RequestedType.class)in the code that needs it
The Bookshelf worked example: the catalogue page size
The Bookshelf catalogue shows ten books per page on a desktop and four on a phone. Application code does not test
Window.getClientWidth(); it asks for a PageSizer, and the compiler wires in the right class per permutation. The
user-defined property formfactor is described in Property providers.
public interface PageSizer {
int pageSize();
String name();
}
public class DesktopPageSizer implements PageSizer {
@Override public int pageSize() { return 10; }
@Override public String name() { return "desktop"; }
}
public class MobilePageSizer extends DesktopPageSizer {
@Override public int pageSize() { return 4; }
@Override public String name() { return "mobile"; }
}
// in the entry point or a view factory
PageSizer sizer = GWT.create(PageSizer.class);
Window.setTitle(sizer.name() + " " + sizer.pageSize());
<module rename-to="rebinddemo">
<inherits name="com.google.gwt.user.User" />
<source path="shared" />
<source path="demo/client" />
<define-property name="formfactor" values="desktop,mobile" />
<!-- property-provider: see "Property providers" below -->
<!-- default first, specific rule after it: the last matching rule wins -->
<replace-with class="com.example.bookshelf.demo.client.DesktopPageSizer">
<when-type-is class="com.example.bookshelf.demo.client.PageSizer" />
</replace-with>
<replace-with class="com.example.bookshelf.demo.client.MobilePageSizer">
<when-type-is class="com.example.bookshelf.demo.client.PageSizer" />
<when-property-is name="formfactor" value="mobile" />
</replace-with>
<entry-point class="com.example.bookshelf.demo.client.RebindDemo" />
</module>
Compiling it with the plain compiler (the command is the one from Compiler & Compilation, with the module name changed)
reported four permutations, two user.agent values times two formfactor values. Opened in headless Chrome, the
entry point wrote these lines into the page:
http://127.0.0.1:18431/index.html -> desktop pageSize=10
http://127.0.0.1:18431/index.html?formfactor=mobile -> mobile pageSize=4
http://127.0.0.1:18431/index.html?formfactor=desktop -> desktop pageSize=10
Swapping the two replace-with elements, so that the unconditional default comes last, shows the rule order: the
default is now examined first, always applies, and the mobile page reports desktop pageSize=10 as well. No error
or warning is printed.
Replacement with a <fail> rule
The module parser also accepts a fail element (it is not in the developer guide or the DTD). It takes the same
conditions as replace-with and aborts the compile when it matches, which is how a module can forbid a combination:
<fail>
<when-type-is class="com.example.bookshelf.demo.client.PageSizer" />
<when-property-is name="formfactor" value="mobile" />
</fail>
With that rule in the demo module the compile stopped with Deferred binding request failed for type
'com.example.bookshelf.demo.client.PageSizer' followed by Failed to resolve 'com.example.bookshelf.demo.client.PageSizer'
via deferred binding on the GWT.create line. Use it sparingly: it is only declared in ModuleDefSchema, not documented.
Generating a type: generate-with
<generate-with class="..."> names a Generator subclass. The compiler instantiates it,
calls it with the requested type name, and uses the class the generator returns as the answer. One generator instance is
created and reused by the compiler. The generator writes the
Java source of that class during the compile, before translation to JavaScript. The registration looks the same as
replace-with, with the same conditions; typically it is when-type-assignable so that one generator serves every
interface that extends a marker type (that is how RemoteService, UiBinder and Localizable work):
<generate-with class="com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator">
<when-type-assignable class="com.google.gwt.user.client.rpc.RemoteService"/>
</generate-with>
The generator class is looked up by name on the compiler’s classpath, so it must be compiled and visible there. A
misspelled class name stops the module load with Unable to load class 'com.example.bookshelf.demo.rebind.DescribrGenerator'
and a ClassNotFoundException trace; the line number points at the generate-with element. Writing one is covered
in Writing a generator.
Conditions
Every condition is a child of replace-with, generate-with, fail or (a subset) set-property. Several direct children are
combined with AND: the rule applies when all of them are true. Compound elements nest.
| Element | True when | Notes |
|---|---|---|
|
the requested type is exactly this class |
Matches only the name given; subclasses do not match. |
|
the requested type is assignable to the class (the same type, a subtype or an implementation) |
A type name the type oracle does not know logs |
|
the current permutation’s value of the property |
The property must already be defined ( |
|
a linker with that registered name is in use |
Accepted by the module parser and listed in the DTD; the developer guide does not describe it. |
|
every child is true (AND) |
The implicit combination of direct children. |
|
at least one child is true (OR) |
The only way to give a rule several values of one property. |
|
no child is true |
The developer guide describes it as NAND; the 2.13.1 source documents it as satisfied only when every child is unsatisfied, which is NOR. |
when-type-is and when-type-assignable are only valid under the rule elements. Inside set-property only the property
conditions are accepted (when-property-is, when-linker-added and all, any, none over them).
<!-- AND (implicit), OR, and NOT-any in one rule -->
<replace-with class="com.example.bookshelf.demo.client.CompactPageSizer">
<when-type-is class="com.example.bookshelf.demo.client.PageSizer" />
<any>
<when-property-is name="formfactor" value="mobile" />
<when-property-is name="formfactor" value="tablet" />
</any>
<none>
<when-property-is name="user.agent" value="safari" />
</none>
</replace-with>
Source: GWT — Organizing Projects: Defining conditions, GWT source (2.13.1) — gwt-module.dtd, GWT source (2.13.1) — ModuleDefSchema.java
Conditions on a rule with a missing property
Because when-property-is only works for a defined property, a rule that refers to a property of a module you do not
inherit fails at module load, not at the GWT.create. Misspelling formfactor as formfactr in the demo stopped the
compile with Line 25: Property 'formfactr' not found. The module that declares the property must be inherited (directly
or through another module) before the rule that uses it.
Properties
A property is a named value the compiler or the browser can read. GWT has two kinds, and the difference matters for permutations.
-
A binding property (also called selection or deferred-binding property) has a fixed list of allowed values. Each combination of values is a permutation, and
when-property-isrules and generators depend on it.user.agentandlocaleare binding properties. -
A configuration property is a compile-time string (or list of strings) that tools and generators read. It never creates permutations.
CssResource.enableGssandlocale.queryparamare configuration properties.
| Element and attributes | Effect | Rules and errors |
|---|---|---|
|
Creates a binding property with these allowed values. |
A binding property may not be redefined: |
|
Adds allowed values. Values accumulate through inherited modules. |
|
|
Fixes the property to this value (or to several, comma-separated), removing the others from the permutations. Optional property conditions make it conditional. |
|
|
Declares the value that the property provider may return for an unknown situation. |
|
|
Gives the browser a JavaScript function body that computes the value, or names a |
A provider needs a body or a generator: |
|
Marks values that share one permutation. |
The values must be defined: |
|
Collapses every binding property into one permutation. |
Optional |
|
Creates a configuration property. |
Redefinition is allowed only with the same |
|
Sets a configuration property (replaces the value). |
An undefined property is created, with a warning: |
|
Appends a value to a multi-valued property. |
|
|
Removes every value. |
The property must exist and be a configuration property. |
The developer guide lists only define-property, extend-property, set-property and property-provider, and
describes deferred binding as rarely needed in user projects. The remaining elements are from the module parser and the DTD.
There is no element called property-fallback-value: the fallback is the fallback-value attribute of
extend-property and the set-property-fallback element.
Fixing and fallback values
set-property is the main tool for shorter development builds. It removes values from the permutation space. The guide
calls the result a "working module": it produces only the variant you test. Every number below is the Compiling n permutations line
of a real compile of the demo module.
| Module change | Permutations | Why |
|---|---|---|
none |
4 |
2 |
|
12 |
x 3 locales ( |
plus |
6 |
|
plus |
1 |
one program for everything |
|
1 |
a working module |
|
2 |
a comma-separated list keeps both |
12-permutation module plus |
10 |
|
Collapsing still works at run time: the collapsed demo still printed mobile pageSize=4 for ?formfactor=mobile and
desktop pageSize=10 for ?formfactor=desktop, from 6 files instead of 12. A collapsed property becomes a "soft
permutation", so the file contains the code of each answer and picks at run time. Collapsing trades download size for
fewer files and a faster compile.
<extend-property name="locale" values="en,es" />
<!-- only the safari permutation is built for Spanish -->
<set-property name="user.agent" value="safari">
<when-property-is name="locale" value="es" />
</set-property>
<collapse-property name="formfactor" values="*" />
Source: GWT — Organizing Projects: Restricting the number of permutations, GWT source (2.13.1) — ModuleDefSchema.java
The fallback value exists for new values. extend-property with fallback-value says "if no rule matches this value
exactly, use the rule of that other value". In a test module, formfactor was extended with tablet and fallback
mobile, the desktop rule was made specific with when-property-is, and no rule mentioned tablet. The compiler
logged, for the PageSizer rebind of the tablet permutations:
Could not find an exact match rule. Using 'closest' rule <replace-with class='com.example.bookshelf.demo.client.MobilePageSizer'/> based on fall back values. You may need to implement a specific binding in case the fall back behavior does not replace the missing binding
In the browser ?formfactor=tablet produced mobile pageSize=4. The compiler also saw that the tablet and mobile
permutations produced identical code and shared one file (Compiling 4 permutations for 3 x 2 combinations). An exact
rule always beats a fallback.
set-property-fallback is a different thing: it sets the value that a provider can use when it cannot decide. In a provider
body the literal text /*-FALLBACK-*/ is replaced by that value when the selection script is generated. It is not applied
automatically: with set-property-fallback declared but a provider that returned watch anyway, nothing was selected.
Property providers
For a binding property whose value is not fixed at compile time the browser must compute it. That is the job of the
provider: a JavaScript function body that returns one of the allowed values (a string), or a PropertyProviderGenerator
class that returns such a body (generator="..."). user.agent uses a generator
(UserAgentPropertyGenerator), locale uses one (LocalePropertyProviderGenerator), and GWT’s mobile sample defines
formfactor with an inline body. A PropertyProviderGenerator implements
generate(TreeLogger, SortedSet<String> possibleValues, String fallback, SortedSet<ConfigurationProperty>) and returns
the JavaScript body as a string.
The Bookshelf provider reads the query parameter and falls back to the window width. The CDATA section is
necessary because the body contains < and &:
<define-property name="formfactor" values="desktop,mobile" />
<property-provider name="formfactor"><![CDATA[
var m = /[?&]formfactor=([^&]+)/.exec($wnd.location.search);
if (m) {
return (m[1] == 'desktop' || m[1] == 'mobile') ? m[1] : '/*-FALLBACK-*/';
}
return $wnd.innerWidth < 600 ? 'mobile' : 'desktop';
]]></property-provider>
<set-property-fallback name="formfactor" value="desktop" />
With this provider ?formfactor=watch renders desktop pageSize=10, because the unknown value was replaced by the
fallback; ?formfactor=mobile gives mobile pageSize=4.
A provider runs in the selection script, before any GWT code is loaded, so it cannot call Java or reference your classes. The script offers a few globals; each one below was used in a compiled test:
-
$wndand$docare the host page’s window and document. -
__gwt_getMetaProperty(name)returns the value of a<meta name="gwt:property" content="name=value">tag of the host page, ornull. -
__gwt_isKnownPropertyValue(name, value)tells whether a value is one of the allowed ones.
<property-provider name="formfactor"><![CDATA[
var v = __gwt_getMetaProperty('formfactor');
return (v && __gwt_isKnownPropertyValue('formfactor', v)) ? v : '/*-FALLBACK-*/';
]]></property-provider>
<meta name="gwt:property" content="formfactor=mobile">
<script src="rebinddemo/rebinddemo.nocache.js"></script>
Together these let the server decide: a servlet or Thymeleaf/JSP template renders the meta tag from its own device
detection, and the compiled application obeys it. With the meta tag formfactor=mobile the page reported mobile
pageSize=4, with formfactor=watch plus the fallback token desktop pageSize=10.
If the provider returns a value that is not allowed, the selection script calls the handler named by a
gwt:onPropertyErrorFn meta tag, if any, with the property name, the list of allowed values and the bad value, and then
throws. The handler must be an expression, so wrap a function in parentheses:
<meta name="gwt:property" content="formfactor=watch">
<meta name="gwt:onPropertyErrorFn"
content="(function(name, allowed, bad) { document.title = 'bad ' + name + ' [' + allowed + '] ' + bad; })">
In a test with a provider that returned the meta value unchecked, the document title became bad formfactor
[desktop,mobile] watch and the application did not start. Without a handler and without the checking code of the
previous provider the page stayed empty and, in the DOM dump, the loader had requested a file called
undefined.cache.js; that silent failure is why a provider should validate its result.
Source: GWT — Organizing Projects: property-provider, GWT source (2.13.1) — sample FormFactor.gwt.xml, GWT source (2.13.1) — properties.js
The developer guide adds that set-property and property-provider on the same property overwrite each other and the
last definition wins. Do not mix them for one property in the same module.
Configuration properties
Configuration properties carry settings to compile-time code: generators, linkers and the CSS and i18n tooling. They are declared once and set anywhere downstream:
<define-configuration-property name="bookshelf.loanDays" is-multi-valued="false" />
<set-configuration-property name="bookshelf.loanDays" value="21" />
<!-- a list: every extend adds one more value -->
<define-configuration-property name="bookshelf.reportColumns" is-multi-valued="true" />
<extend-configuration-property name="bookshelf.reportColumns" value="title" />
<extend-configuration-property name="bookshelf.reportColumns" value="author" />
Setting a configuration property without defining it works but logs the warning shown in the element table; always
define it. A generator reads one with the property oracle, as in the BuildTagGenerator of Writing a generator, where
locale.queryparam came back as locale. Typical built-in ones, from the 2.13.1 module files, are CssResource.enableGss
and CssResource.conversionMode (set in the Bookshelf module), ClientBundle.enableRenaming, locale.queryparam,
locale.cookie, locale.useragent, locale.usemeta, locale.searchorder, runtime.locales and user.agent.runtimeWarning.
user.agent
user.agent is defined in UserAgent.gwt.xml and today it has two values: gecko1_8 and safari. The module
defines the first, extends it with the second and attaches the generator-based provider. Browsers that are
neither identify as one of them: the provider tests the lower-cased user-agent string for webkit (then safari, which
includes Chrome and current Edge) and then for gecko (then gecko1_8), otherwise it returns the property’s fallback value, which for user.agent is empty and therefore not an allowed value.
The values ie6, ie8, ie9, ie10, opera and plain gecko that the developer guide and the book mention are gone; Internet
Explorer 11 identifies itself with gecko and so selects gecko1_8. A rule that names a removed value is rejected when
the module loads: Value 'ie8' in not a valid value for property 'user.agent'.
Two related settings live in the same module. webApiUsage is a binding property with the values stable and modern
(default modern) that lets widgets use not-yet-standardised web APIs. user.agent.runtimeWarning is a configuration property,
true by default; the module comment says to set it to false to avoid runtime warnings about a mismatch between the
compile-time and the runtime value. The check is done by the entry point UserAgentAsserter. When you build a single-permutation test
with -setProperty user.agent=gecko1_8 and open the page in a WebKit browser, the asserter throws:
Uncaught Error: com.google.gwt.useragent.client.UserAgentAsserter$UserAgentAssertionError: Possible problem with your *.gwt.xml module file.
The compile time user.agent value (gecko1_8) does not match the runtime user.agent value (safari).
Expect more errors.
(reproduced in headless Chrome with the demo module). Use the other user.agent values only in per-browser test builds,
and never ship a single-agent build.
locale
locale is a binding property defined in I18N.gwt.xml with the single value default; the application extends it, as
the Bookshelf module does:
<inherits name="com.google.gwt.i18n.I18N" />
<extend-property name="locale" values="en" />
<extend-property name="locale" values="es" />
The module also declares <set-property-fallback name="locale" value="default"/>, the fallback that its provider
generator can substitute for a locale it does not know. Its provider is generated by LocalePropertyProviderGenerator, and five configuration properties
steer where the browser looks for the value:
| Configuration property | Default | Meaning |
|---|---|---|
|
|
Name of the URL query parameter ( |
|
empty |
Cookie name; empty means no cookie lookup. |
|
|
Whether to use the browser language. |
|
|
Whether to read |
|
|
Order in which the sources are tried. |
Every new locale multiplies the permutations (see the permutation counts table), and it also changes which implementation of
every Localizable interface (Messages, Constants) the generator produces. Locale inheritance, plural forms, the
Dictionary class for dynamic look-up and DateTimeFormat/NumberFormat belong to the Internationalization page. The point for this page is that locale and user.agent are ordinary binding properties, and
everything said about conditions, fixing and collapsing applies to them. The exact search order is also the answer to a
common surprise: with <meta name="gwt:property" content="locale=en"> in the host page and ?locale=es in the URL, the
page showed Spanish because the query parameter comes first.
Writing a generator
A generator is a class that extends com.google.gwt.core.ext.Generator (or IncrementalGenerator). When a rule
selects it, the compiler passes it the requested type name and expects the name of a class that now exists. It has
three duties: read the structure of the requested type from the type oracle, write a Java class with a SourceWriter,
and return its fully qualified name. Returning null means "use the requested type itself".
| Type | Role |
|---|---|
|
Abstract base. |
|
Optional annotation; |
|
Subclass with |
|
The compiler’s services: |
|
The parsed source: |
|
The |
|
|
|
Hierarchical log: |
|
In |
|
Thrown after logging an |
A Bookshelf example: BookDescriber
The scenario needs a way to print a book for audit logs without writing and maintaining a toString() for every shared
entity. Describer<T> is the client-side contract; the generator creates the implementation for any interface that
extends it, by reading the getters of T from the type oracle. The client side:
package com.example.bookshelf.demo.client;
/** Renders a value of type T as text. Implementations are generated at compile time. */
public interface Describer<T> {
String describe(T value);
}
public interface BookDescriber extends Describer<Book> {}
// use
BookDescriber describer = GWT.create(BookDescriber.class);
String line = describer.describe(new Book(7, "Dune", "Frank Herbert", 1965, 3));
The generator lives in a rebind package that is not on the module’s source path:
package com.example.bookshelf.demo.rebind;
public class DescriberGenerator extends Generator {
@Override
public String generate(TreeLogger logger, GeneratorContext context, String typeName)
throws UnableToCompleteException {
TypeOracle oracle = context.getTypeOracle();
JClassType requested = oracle.findType(typeName);
if (requested == null || requested.isInterface() == null) {
logger.log(TreeLogger.ERROR, typeName + " must be an interface");
throw new UnableToCompleteException();
}
JClassType described = describedType(logger, requested); // the T of Describer<T>
String pkg = requested.getPackage().getName();
String simple = requested.getName().replace('.', '_') + "Impl";
String qualified = pkg + "." + simple;
PrintWriter out = context.tryCreate(logger, pkg, simple);
if (out == null) {
return qualified; // already generated for another permutation: reuse it
}
ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(pkg, simple);
factory.addImplementedInterface(requested.getQualifiedSourceName());
SourceWriter w = factory.createSourceWriter(context, out);
w.println("public String describe(%s value) {", described.getQualifiedSourceName());
w.indent();
w.println("StringBuilder sb = new StringBuilder(\"%s{\");", described.getSimpleSourceName());
boolean first = true;
for (JMethod m : described.getInheritableMethods()) {
if (!isGetter(m)) {
continue;
}
w.println("sb.append(\"%s%s=\").append(value.%s());", first ? "" : ", ",
escape(propertyName(m)), m.getName());
first = false;
logger.log(TreeLogger.INFO, "Describing " + described.getName() + "." + m.getName() + "()");
}
w.println("return sb.append('}').toString();");
w.outdent();
w.println("}");
w.commit(logger); // the file is only written when committed
return qualified;
}
// describedType(), isGetter() and propertyName() inspect JParameterizedType.getTypeArgs()
// and the names of public no-argument get* methods
}
Registered in the module XML next to the rules above:
<generate-with class="com.example.bookshelf.demo.rebind.DescriberGenerator">
<when-type-assignable class="com.example.bookshelf.demo.client.Describer" />
</generate-with>
The compile printed the generator’s own INFO lines inside the tree that the compiler builds for the rebind:
Compiling module com.example.bookshelf.RebindDemo
Computing all possible rebind results for 'com.example.bookshelf.demo.client.BookDescriber'
Rebinding com.example.bookshelf.demo.client.BookDescriber
Invoking generator com.example.bookshelf.demo.rebind.DescriberGenerator
Describing Book.getAuthor()
Describing Book.getCopies()
Describing Book.getId()
Describing Book.getTitle()
Describing Book.getYear()
Compiling 4 permutations
and -gen out/a/gen (a compiler flag that writes the generated sources) produced the class below. The type oracle
returned the methods in alphabetical order, not in declaration order, so never depend on source order:
package com.example.bookshelf.demo.client;
public class BookDescriberImpl implements com.example.bookshelf.demo.client.BookDescriber {
public String describe(com.example.bookshelf.shared.Book value) {
StringBuilder sb = new StringBuilder("Book{");
sb.append("author=").append(value.getAuthor());
sb.append(", copies=").append(value.getCopies());
sb.append(", id=").append(value.getId());
sb.append(", title=").append(value.getTitle());
sb.append(", year=").append(value.getYear());
return sb.append('}').toString();
}
}
In the browser the entry point wrote Book{author=Frank Herbert, copies=3, id=7, title=Dune, year=1965}.
Where generator code lives
The generator is plain Java for the JVM that runs the compiler. It is compiled by javac like any server class and
may use the whole JDK, but it must not be on the module source path. With <source path="demo"/> instead of demo/client, the
rebind package became GWT source and the lenient compiler printed only
Ignored 1 unit with compilation errors in first pass. In strict mode (-failOnError) it then printed a long list:
Tracing compile failure path for type 'com.example.bookshelf.demo.rebind.DescriberGenerator'
[ERROR] Errors in 'file:/.../DescriberGenerator.java'
[ERROR] Line 17: No source code is available for type com.google.gwt.core.ext.Generator; did you forget to inherit a required module?
[ERROR] Line 20: No source code is available for type com.google.gwt.core.ext.TreeLogger; did you forget to inherit a required module?
...
Aborting compile due to errors in some input files
By convention the package is called rebind, and it sits beside client and shared in the project. In a Maven project
the class must be compiled by javac and have gwt-dev on its compile classpath (the client module of the archetype
already depends on gwt-dev).
A generator that reads properties
A generator can read the permutation’s property values through the PropertyOracle. The output of such a generator
differs per permutation, so the compiler runs it for every permutation. The test generator below writes one class per
combination:
PropertyOracle properties = context.getPropertyOracle();
try {
String agent = properties.getSelectionProperty(logger, "user.agent").getCurrentValue();
String formFactor = properties.getSelectionProperty(logger, "formfactor").getCurrentValue();
String queryParam = properties.getConfigurationProperty("locale.queryparam").getValues().get(0);
// ... tryCreate("BuildTag_" + agent + "_" + formFactor), then
w.println("public String tag() { return \"%s/%s/%s\"; }", agent, formFactor, escape(queryParam));
} catch (BadPropertyValueException e) {
logger.log(TreeLogger.ERROR, "Missing property: " + e.getMessage(), e);
throw new UnableToCompleteException();
}
Its log shows four invocations, one per permutation, whereas the property-independent DescriberGenerator above logged once
(its later invocations found the class already created and returned early with nothing to print):
Rebinding com.example.bookshelf.demo.client.BuildTag
Invoking generator com.example.bookshelf.demo.rebind.BuildTagGenerator
user.agent=gecko1_8, formfactor=desktop
Rebinding com.example.bookshelf.demo.client.BuildTag
Invoking generator com.example.bookshelf.demo.rebind.BuildTagGenerator
user.agent=safari, formfactor=desktop
Rebinding com.example.bookshelf.demo.client.BuildTag
Invoking generator com.example.bookshelf.demo.rebind.BuildTagGenerator
user.agent=gecko1_8, formfactor=mobile
Rebinding com.example.bookshelf.demo.client.BuildTag
Invoking generator com.example.bookshelf.demo.rebind.BuildTagGenerator
user.agent=safari, formfactor=mobile
Four classes were generated (BuildTag_gecko1_8_desktop, BuildTag_gecko1_8_mobile, BuildTag_safari_desktop,
BuildTag_safari_mobile), and in headless Chrome (WebKit, so safari) the page showed safari/mobile/locale and
safari/desktop/locale for the two form factors.
RunsLocal and IncrementalGenerator
@Generator.RunsLocal tells the compiler that a generator only uses local information, so it can run as early as
possible in the incremental compile tree. The Javadoc states two restrictions: it may not use global type information
such as JClassType.getSubTypes() or TypeOracle.getTypes(), and it may only read the properties listed in
requiresProperties (RunsLocal.ALL allows any, at a speed cost). The restriction is enforced. Annotating the
property-reading generator above with a plain @RunsLocal failed the compile:
[ERROR] Generator 'com.example.bookshelf.demo.rebind.BuildTagLocalGenerator' threw an exception while rebinding 'com.example.bookshelf.demo.client.BuildTag'
java.lang.IllegalStateException: Access to binding property 'user.agent' is not allowed. com.example.bookshelf.demo.rebind.BuildTagLocalGenerator's RunsLocal annotation may need to be updated.
The Bookshelf DescriberGenerator reads no properties, so it can carry the annotation. Written as an
IncrementalGenerator it looks like this and produced the same behaviour in the demo:
@RunsLocal
public class IncrementalDescriberGenerator extends IncrementalGenerator {
private static final long VERSION = 1L; // bump it when the generated code changes
@Override
public long getVersionId() {
return VERSION;
}
@Override
public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
// ... same type-oracle code as DescriberGenerator ...
PrintWriter out = context.tryCreate(logger, pkg, simple);
if (out == null) {
return new RebindResult(RebindMode.USE_EXISTING, pkg + "." + simple);
}
// ... write the class with a SourceWriter, then commit ...
return new RebindResult(RebindMode.USE_ALL_NEW, pkg + "." + simple);
}
}
| Value | Meaning (Javadoc) |
|---|---|
|
Nothing new was generated; the requested type, or a class that already exists, is the answer. |
|
Only newly generated output is used and all of it is cached. |
|
Only new output, nothing cached. What a plain |
|
Nothing new; the cached output of a previous run is reused. |
|
A mix: types marked with a successful |
Be realistic about the benefit. isGeneratorResultCachingEnabled() is false unless a caller switches it on, and in
the 2.13.1 sources the only code that does is ShellModuleSpaceHost, the classic Dev Mode host. So the caching half of
IncrementalGenerator is of little use for a new generator, while getVersionId() is what invalidates a stale cached
result when you change the generator. A plain Generator that cooperates with tryCreate is enough for application code. The generator instance is kept
and reused by the compiler, so it must be thread-safe and must not keep per-rebind state in fields.
Why generators are discouraged for new code
The GWT library is full of generators, but writing a new one is a last resort today, for these reasons.
-
They tie code to the GWT compiler. Deferred binding and
com.google.gwt.core.ext.typeinfobelong to the GWT compiler. J2CL, Google’s separate Java-to-JavaScript transpiler, is described in its README as a product started from scratch rather than an incremental successor, and its README documents neitherGWT.create()nor generators. Treat both as GWT-only, and write code that must also run on J2CL with JsInterop, Elemental2 and plain factories (see the J2CL & the Future). -
They slow the build. A generator may touch any type, so the compiler cannot run it early or cache it freely. The
GeneratorJavadoc offers@RunsLocalfor exactly this reason, to limit the impact on compilation speed. -
They are hard to debug and to read. Failures appear as a failed rebind on a
GWT.createline, the generated source only exists under-gen, and the type-oracle model is its own API. -
Standard Java tools do the same job. An annotation processor runs in
javac, writes real source files that your IDE and code review can see, and works for both GWT and J2CL. GWT itself shipsrequestfactory-aptfor RequestFactory, and Dagger publishesdagger-gwt.
Prefer, in this order: a hand-written factory, a replace-with rule over a property when only a choice of implementation
is needed, an annotation processor when code must be derived from types, and a generator only when the code depends on a
GWT property or on another GWT.create result (as Messages does with locale). The type model is also different
from what the 2007 book describes: its generators read Javadoc metadata tags such as @gwt.typeArgs, which no longer
exist; use annotations (JClassType.getAnnotation) and generics (JParameterizedType.getTypeArgs()).
Pitfalls
| Symptom (messages quoted as printed) | Cause | Fix |
|---|---|---|
|
|
Add a |
|
The argument is a variable or expression. |
Pass |
|
The replacement class has only a parameterised constructor, or is abstract. |
Give it a public no-argument constructor and make it concrete. |
|
The |
Make the replacement implement or extend the requested type. |
The |
The unconditional default rule was declared after the specific one. The rule declared last is examined first. |
Declare the default first, and the more specific rules after it. |
|
|
Fix the name and |
|
|
Use |
|
A second |
Use |
|
|
Define the value first (in |
|
The generator class name in |
Fix the name; make sure the module that builds the generator ran before the GWT compile. |
|
The generator is inside a package on the module’s source path, so GWT tries to translate it. |
Keep generators in |
|
|
List the properties in |
|
The generator logged an error and threw |
Call |
|
A |
Remove the combination or provide a rule that handles it. |
An empty page, and a request for |
The property provider returned a value that is not in the allowed list. |
Validate with |
|
A test build with |
Use the single-agent build only in the matching browser; ship all permutations. |
|
Every binding property and locale multiplies the permutation count. |
|
|
A plain JUnit test (or server code) reached a |
Test the logic without the widget, or extend |
What changed since the older material
-
Browsers. The developer guide’s samples and the 2007 book show rules for
ie6,ie8,operaand plaingecko; onlygecko1_8andsafariremain, and the realPopup.gwt.xmlandFocus.gwt.xmlof 2.13.1 are much smaller than the guide’s examples. A page that still names an old value does not load. -
Guide wording. The deferred-binding page speaks of
generates-withelements, the module-XML page ofreplace-with-classandgenerate-with-class, and the module-XML page describesnoneas NAND; the element names arereplace-withandgenerate-with, andnoneis NOR. Follow the module parser and the DTD of 2.13.1. -
Generators. The book’s generator chapter (chapter 14) uses Javadoc metadata tags and the
Generator,TypeOracle,JClassType,SourceWriterandTreeLoggertypes that still exist.Generator.RunsLocalandIncrementalGeneratorcame later; Javadoc metadata is gone in favour of annotations and generics. -
Properties in the book (chapter 15). The user-defined property with a JavaScript provider,
extend-propertyfor locales anduser.agent-driven classes all still work (checked above).Dictionary,ConstantsandMessagesare covered on the Internationalization page. -
Development. The book’s hosted mode is gone; the rules above are evaluated by the GWT compiler (run here from the command line).
The Manning book GWT in Action (2007, GWT 1.3/1.4) was consulted for the concept coverage of modules, properties and generators; its code and examples are not reproduced and are outdated in the points above.