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.js file of the mobile permutation contains the string mobile once and desktop not at all, and the desktop file 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.

Table 1. Where the GWT library itself uses deferred binding (2.13.1 module files)
You call GWT.create() on Module file Rule

a RemoteService interface

RemoteService.gwt.xml

generate-with ServiceInterfaceProxyGenerator for every type assignable to RemoteService

a UiBinder or UiRenderer

UiBinder.gwt.xml

generate-with UiBinderGenerator

a Messages, Constants or other Localizable interface

I18N.gwt.xml

generate-with LocalizableGenerator for every type assignable to Localizable

a ClientBundle

Resources.gwt.xml

StaticClientBundleGenerator, overridden by InlineClientBundleGenerator when ClientBundle.enableInlining is true

a PlaceHistoryMapper

Place.gwt.xml

generate-with PlaceHistoryMapperGenerator

a SimpleBeanEditorDriver

Editor.gwt.xml

generate-with SimpleBeanEditorDriverGenerator

a SafeHtmlTemplates

SafeHtml.gwt.xml

generate-with SafeHtmlTemplatesGenerator

an AutoBeanFactory or a RequestFactory

AutoBean.gwt.xml, RequestFactory.gwt.xml

a generator each

a javax.validation.Validator

Validation.gwt.xml

generate-with ValidatorGenerator

the PopupImpl helper behind PopupPanel

Popup.gwt.xml

replace-with PopupImplMozilla when user.agent is gecko1_8

the FocusImpl helper behind focus widgets

Focus.gwt.xml

replace-with FocusImplStandard for gecko1_8 and FocusImplSafari for safari

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.

flowchart TD call["GWT.create(T.class)
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-with class that does not implement the requested interface compiles and then fails in the browser with Uncaught 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>

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":

  1. an interface or base class that application code uses (the requested type)

  2. a default implementation and one subclass or sibling per variant

  3. one replace-with per variant, each with the property it depends on

  4. 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.

Table 2. Conditions the module parser accepts (ModuleDefSchema, 2.13.1)
Element True when Notes

<when-type-is class="..."/>

the requested type is exactly this class

Matches only the name given; subclasses do not match.

<when-type-assignable class="..."/>

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 Unknown type '...' specified in deferred binding rule as a warning and the condition is false.

<when-property-is name="p" value="v"/>

the current permutation’s value of the property p is v

The property must already be defined (Property 'p' not found otherwise). Also works with a configuration property: it then compares against the first configured value.

<when-linker-added name="x"/>

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.

<all> ... </all>

every child is true (AND)

The implicit combination of direct children.

<any> ... </any>

at least one child is true (OR)

The only way to give a rule several values of one property.

<none> ... </none>

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>

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-is rules and generators depend on it. user.agent and locale are binding properties.

  • A configuration property is a compile-time string (or list of strings) that tools and generators read. It never creates permutations. CssResource.enableGss and locale.queryparam are configuration properties.

Table 3. Property elements of the module XML (ModuleDefSchema, 2.13.1)
Element and attributes Effect Rules and errors

<define-property name="p" values="a,b"/>

Creates a binding property with these allowed values.

A binding property may not be redefined: The deferred-binding property named formfactor may not be redefined. The name must be a valid identifier path: Invalid property name 'bad name'.

<extend-property name="p" values="c" fallback-value="a"/>

Adds allowed values. Values accumulate through inherited modules. fallback-value (optional) names an existing value that stands in for the new ones.

The property formfactor fallback-value was not defined when the fallback is not a defined value. The standard use is locale.

<set-property name="p" value="a"/>

Fixes the property to this value (or to several, comma-separated), removing the others from the permutations. Optional property conditions make it conditional.

Value 'tablet' in not a valid value for property 'formfactor' (sic) for an undefined value.

<set-property-fallback name="p" value="a"/>

Declares the value that the property provider may return for an unknown situation.

The fallback value 'x' was not previously defined for property 'p'. It is substituted for the token the fallback token in a provider body.

<property-provider name="p" generator="..."> ... </property-provider>

Gives the browser a JavaScript function body that computes the value, or names a PropertyProviderGenerator that writes it.

A provider needs a body or a generator: Property providers must specify a JavaScript body or a provider generator.

<collapse-property name="p" values="*"/>

Marks values that share one permutation. * is a glob.

The values must be defined: The value x was not previously defined for the property p.

<collapse-all-properties/>

Collapses every binding property into one permutation.

Optional value (true by default).

<define-configuration-property name="c" is-multi-valued="false"/>

Creates a configuration property.

Redefinition is allowed only with the same is-multi-valued.

<set-configuration-property name="c" value="v"/>

Sets a configuration property (replaces the value).

An undefined property is created, with a warning: Setting configuration property named 'bookshelf.pageLimit' in module '...P28' that has not been previously defined.

<extend-configuration-property name="c" value="v"/>

Appends a value to a multi-valued property.

The property c does not support multiple values for a single-valued one.

<clear-configuration-property name="c"/>

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.

Table 4. Permutation counts of the Bookshelf demo module (two user.agent values, two formfactor values)
Module change Permutations Why

none

4

2 user.agent x 2 formfactor

locale extended with en and es

12

x 3 locales (default, en, es)

plus collapse-property on formfactor, values *

6

formfactor no longer multiplies; the choice is made inside one file

plus collapse-all-properties

1

one program for everything

set-property user.agent=safari and formfactor=desktop

1

a working module

set-property user.agent to safari,gecko1_8 and formfactor=desktop

2

a comma-separated list keeps both

12-permutation module plus set-property user.agent=safari conditioned on locale=es

10

es keeps one user.agent, the other two locales keep both

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="*" />

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:

  • $wnd and $doc are 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, or null.

  • __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.

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

locale.queryparam

locale

Name of the URL query parameter (?locale=es).

locale.cookie

empty

Cookie name; empty means no cookie lookup.

locale.useragent

N

Whether to use the browser language.

locale.usemeta

Y

Whether to read <meta name="gwt:property" content="locale=es">.

locale.searchorder

queryparam,cookie,meta,useragent

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".

Table 5. The generator API, with the types you meet first
Type Role

Generator

Abstract base. String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException. Subclasses must be thread-safe. Static helpers escape(String) and escapeClassName(String).

Generator.RunsLocal

Optional annotation; requiresProperties lists the properties the generator reads. See below.

IncrementalGenerator

Subclass with RebindResult generateIncrementally(...) and long getVersionId(); its generate is final.

GeneratorContext

The compiler’s services: getTypeOracle(), getPropertyOracle(), getResourcesOracle(), tryCreate(logger, packageName, simpleName), commit(logger, PrintWriter), tryCreateResource, commitResource, commitArtifact, isProdMode(), checkRebindRuleAvailable(String).

TypeOracle

The parsed source: findType(String) (returns null if absent), getType(String) (throws NotFoundException), getTypes(), getPackages(), parse(String) for generics.

JClassType, JMethod, JField, JParameterizedType, JPrimitiveType

The com.google.gwt.core.ext.typeinfo model: isInterface(), getImplementedInterfaces(), getInheritableMethods(), getMethods(), getFields(), isParameterized(), getTypeArgs(), annotations through getAnnotation(Class).

PropertyOracle

getSelectionProperty(logger, name) (getCurrentValue(), getPossibleValues(), getFallbackValue()) and getConfigurationProperty(name) (getValues()); both throw BadPropertyValueException.

TreeLogger

Hierarchical log: log(TreeLogger.Type, message), branch(...). A branch is only printed when something inside it logs at the visible level.

ClassSourceFileComposerFactory, SourceWriter

In com.google.gwt.user.rebind. The factory takes the package and class name, addImplementedInterface, addImport, setSuperclass, and createSourceWriter(context, printWriter). The writer has println, indent, outdent, indentln and commit(logger).

UnableToCompleteException

Thrown after logging an ERROR to abort the rebind cleanly.

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);
  }
}
Table 6. RebindMode values
Value Meaning (Javadoc)

USE_EXISTING

Nothing new was generated; the requested type, or a class that already exists, is the answer.

USE_ALL_NEW

Only newly generated output is used and all of it is cached.

USE_ALL_NEW_WITH_NO_CACHING

Only new output, nothing cached. What a plain Generator is wrapped into.

USE_ALL_CACHED

Nothing new; the cached output of a previous run is reused.

USE_PARTIAL_CACHED

A mix: types marked with a successful GeneratorContext.tryReuseTypeFromCache come from the cache, the rest is new.

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.typeinfo belong 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 neither GWT.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 Generator Javadoc offers @RunsLocal for 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.create line, 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 ships requestfactory-apt for RequestFactory, and Dagger publishes dagger-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

Rebind result 'com.example.bookshelf.demo.client.PageSizer' must be a class

GWT.create() of an interface or abstract type that no rule covers.

Add a replace-with or generate-with rule, and inherit the module that declares it.

Only class literals may be used as arguments to GWT.create()

The argument is a variable or expression.

Pass Type.class. A Class variable may appear to work in the old Dev Mode but not in compiled code.

Rebind result '...LockedPageSizer' has no default (zero argument) constructors and Rebind result '...AbstractPageSizer' cannot be abstract

The replacement class has only a parameterised constructor, or is abstract.

Give it a public no-argument constructor and make it concrete.

Uncaught Error: java.lang.ClassCastException at start-up with no compile error

The replace-with class is not assignable to the requested type; the compiler does not check it.

Make the replacement implement or extend the requested type.

The mobile page uses the default implementation; no message

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.

Line 25: Property 'formfactr' not found

when-property-is names an undefined property (misspelled, or its module is not inherited).

Fix the name and inherits the defining module.

Value 'ie8' in not a valid value for property 'user.agent'

set-property or a condition uses a value the property does not define; ie6 to ie10 and opera no longer exist.

Use gecko1_8 or safari. Note the typo "in" in GWT’s own message.

The deferred-binding property named formfactor may not be redefined. and Invalid property name 'bad name'

A second define-property for the same name, or a name that is not a dotted identifier.

Use extend-property to add values. Use dot-separated Java identifiers as names.

The property formfactor fallback-value was not defined

fallback-value names a value that formfactor does not have.

Define the value first (in define-property or an earlier extend-property).

Unable to load class 'com.example.bookshelf.demo.rebind.DescribrGenerator' and a ClassNotFoundException

The generator class name in generate-with is misspelled, or its .class is not on the compiler classpath.

Fix the name; make sure the module that builds the generator ran before the GWT compile.

Ignored 1 unit with compilation errors in first pass. then No source code is available for type com.google.gwt.core.ext.Generator

The generator is inside a package on the module’s source path, so GWT tries to translate it.

Keep generators in rebind outside the source paths.

Generator '...' threw an exception while rebinding '...' with Access to binding property 'user.agent' is not allowed. ...'s RunsLocal annotation may need to be updated.

@RunsLocal without requiresProperties for a generator that reads properties.

List the properties in requiresProperties, or drop the annotation.

Failed to resolve 'com.example.bookshelf.demo.client.BrokenDescriber' via deferred binding, with BrokenDescriber must be an interface

The generator logged an error and threw UnableToCompleteException, because the requested type is a class.

Call GWT.create on the interface that extends Describer.

Deferred binding request failed for type '...PageSizer'

A fail rule matched.

Remove the combination or provide a rule that handles it.

An empty page, and a request for undefined.cache.js in the DOM

The property provider returned a value that is not in the allowed list.

Validate with __gwt_isKnownPropertyValue and return the fallback token, or set gwt:onPropertyErrorFn to see the bad value.

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).

A test build with -setProperty user.agent=gecko1_8 was opened in a WebKit browser.

Use the single-agent build only in the matching browser; ship all permutations.

Compiling 12 permutations and a slow build after adding a property or locale

Every binding property and locale multiplies the permutation count.

set-property for local builds, collapse-property for properties that only choose behaviour, fewer values, and see Compiler & Compilation.

ERROR: GWT.create() is only usable in client code! in a unit test

A plain JUnit test (or server code) reached a GWT.create() call.

Test the logic without the widget, or extend GWTTestCase (see Testing), or register an instantiator with ServerGwtBridge for shared code.

What changed since the older material

  • Browsers. The developer guide’s samples and the 2007 book show rules for ie6, ie8, opera and plain gecko; only gecko1_8 and safari remain, and the real Popup.gwt.xml and Focus.gwt.xml of 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-with elements, the module-XML page of replace-with-class and generate-with-class, and the module-XML page describes none as NAND; the element names are replace-with and generate-with, and none is 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, SourceWriter and TreeLogger types that still exist. Generator.RunsLocal and IncrementalGenerator came 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-property for locales and user.agent-driven classes all still work (checked above). Dictionary, Constants and Messages are 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.