ClientBundle & Images

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 (net.ltgt.gwt.maven), JDK 21 running the compiler with -sourceLevel 17, the Jakarta Servlet 6 server of the Bookshelf scenario and the org.gwtproject:gwt BOM, checked on 2026-10-09. ClientBundle is client code with a large build-time half and a small server half: you declare an interface in Java, a generator that runs inside the GWT compiler reads the files it names and decides how each one reaches the browser, and the web server that delivers the compiled application must send the right HTTP caching headers for the strong-named files the generator writes. The module that provides it, com.google.gwt.resources.Resources, is inherited by com.google.gwt.user.UI and therefore by every module that inherits com.google.gwt.user.User, so the Bookshelf client needs no extra dependency. Every listing on this page was compiled by the 2.13.1 compiler in a private copy of the Bookshelf scenario; the compiler output, the generated JavaScript, the files in the war directory, the DOM seen by headless Chrome 155 and the HTTP headers served by Jetty 12.1.10 are quoted as they were printed (Super Dev Mode used code server port 9912 and the web server port 8912).

A web application is mostly made of files that are not code: logos, icons, stylesheets, help texts, templates for download. Delivering them naively costs one HTTP request each, freshness checks for every cached copy, and file names that can silently drift away from the Java code that mentions them. ClientBundle turns each of those files into a typed Java method that the compiler checks, and then packages the file in the best way the browser and the file size allow: inside the JavaScript as a string or a data: URL, composited with other images, or as a content-addressed file that can be cached for a year.

Why ClientBundle exists

The Developer Guide divides the files of a deployed GWT application into three groups by how they should be cached:

Table 1. The three caching classes of a GWT application
Class Files Right caching policy

Never cache

*.nocache.js, the bootstrap script, for example bookshelf.nocache.js

Revalidate on every visit; it is the small file that decides which permutation to load.

Cache forever

the compiled permutations (<hash>.cache.js) and the strong-named resource files (<hash>.cache.png, <hash>.cache.csv)

The name is a hash of the content, so a changed file has a new name; a far-future expiry is safe.

Everything else

a hand-written app.css, logo.png in the host page, help.txt

No safe policy: if you cache it, users may keep an old copy after a release; if you do not, you pay a request and a freshness check every time.

ClientBundle moves the "everything else" files into the "cache forever" class (or into the JavaScript itself, which is cached forever anyway). The Developer Guide lists the goals: no uncertainty about whether the application gets the right contents, less non-determinism caused by intermediate proxy servers, more aggressive caching headers, consistency checks between file names and Java code at compile time, data: URLs and other embedding where the browser and size allow it, and an extensible design for new resource types. The "non-goals" entry says it is not a file-system abstraction: a bundle holds files known at build time, not user uploads. Book covers uploaded by librarians to the Bookshelf catalogue are fetched by URL at run time; the placeholder shown while a book has no cover is a bundle resource.

The model: a bundle interface, @Source and resource types

A bundle is an interface that extends com.google.gwt.resources.client.ClientBundle and declares no-argument accessor methods. Each method returns a subtype of ResourcePrototype, and an optional @Source annotation names the file (or files) behind it:

classDiagram class ResourcePrototype <<interface>> ResourcePrototype class ClientBundle <<interface>> ClientBundle class ClientBundleWithLookup <<interface>> ClientBundleWithLookup class ImageResource <<interface>> ImageResource class DataResource <<interface>> DataResource class TextResource <<interface>> TextResource class ExternalTextResource <<interface>> ExternalTextResource class CssResourceBase <<interface>> CssResourceBase class CssResource <<interface>> CssResource class GwtCreateResource~T~ <<interface>> GwtCreateResource~T~ ClientBundle <|-- ClientBundleWithLookup ResourcePrototype <|-- ImageResource ResourcePrototype <|-- DataResource ResourcePrototype <|-- TextResource ResourcePrototype <|-- ExternalTextResource ResourcePrototype <|-- CssResourceBase CssResourceBase <|-- CssResource ResourcePrototype <|-- GwtCreateResource

The smallest useful bundle of the Bookshelf scenario holds the stylesheet and the logo. GWT.create on the interface is ordinary deferred binding (see Deferred Binding & Generators): the module rule in Resources.gwt.xml sends every type assignable to ClientBundle to a generator, and the generator writes the implementation class.

package com.example.bookshelf.client.resources;

import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;

public interface AppResources extends ClientBundle {
  AppResources INSTANCE = GWT.create(AppResources.class);

  @Source("app.gss")
  AppCss css();

  @Source("logo.png")
  ImageResource logo();
}

How the compiler builds a bundle

flowchart TD A["AppResources.java: bundle interface plus Source annotations"] --> B["GWT.create(AppResources.class) during deferred binding"] B --> C{"ClientBundle.enableInlining"} C -- "true, the default" --> D["InlineClientBundleGenerator"] C -- "false" --> E["StaticClientBundleGenerator"] D --> F["one ResourceGenerator per resource type: image, data, text, css, create"] E --> F F --> G["find the file: locale suffixes, then classpath"] G --> H{"deploy the bytes"} H -- "data fits and not DoNotEmbed" --> I["data: URL string inside the permutation JavaScript"] H -- "too big, preventInlining, or inlining off" --> J["write hash.cache.ext next to the permutation"] I --> K["AppResources_locale_InlineClientBundleGenerator Java class in each locale permutation"] J --> K

Three details of this pipeline matter in practice:

  • The generator runs once per combination of the properties the bundle depends on. Each locale value of the module gets its own generated class; in a first probe the compiler produced AppResources_default_InlineClientBundleGenerator, AppResources_en_InlineClientBundleGenerator and AppResources_es_InlineClientBundleGenerator. Once a stylesheet of the bundle tested user.agent (see below), the names became AppResources_safari_en_InlineClientBundleGenerator and so on, because the bundle now depends on that property too. This is what makes locale-specific files possible (see below).

  • Unused accessors are pruned from the JavaScript. When a probe stopped calling banner(), the compiled permutations contained zero references to the banner’s file name. The file itself was still copied into the output directory (see the pitfalls table), so pruning saves script size, not disk space.

  • Generator results are cached between compiles. Since GWT 2.5 the ClientBundle generators support result caching (the 2.5.0 release notes list a significant speedup of development refresh).

@Source: where files are found

@Source takes one or more classpath paths. The generator tries each path first relative to the package of the bundle interface, and, if nothing is found there, as an absolute classpath path (the way ClassLoader.getResource expects it). In the Bookshelf the bundle lives in com.example.bookshelf.client.resources, so @Source("app.gss") resolves to src/main/resources/com/example/bookshelf/client/resources/app.gss, and a file elsewhere can be named @Source("com/example/shared/logo.png"). When @Source is omitted, the accessor name plus the default extensions of the resource type is tried in the bundle’s package:

Table 2. Default extensions when @Source is omitted
Resource type Extensions tried, in order Example

ImageResource

.png, .jpg, .gif, .bmp

ImageResource star(); finds star.png

TextResource, ExternalTextResource

.txt

TextResource about(); finds about.txt

CssResource

.css, .gss

AppCss css(); finds css.css or css.gss

DataResource, GwtCreateResource

none

@Source is required for data files; GwtCreateResource needs no file at all

Only the resource types that read several files accept more than one path (a CssResource concatenates its stylesheets); ImageResource and DataResource must name exactly one, and the generator says so (see the pitfalls table). Resources are located when the compiler runs, so a mistyped name is a compile error rather than a 404 in production.

Accessor rules, nested bundles and lookup by name

  • An accessor has no parameters and returns a type that extends ResourcePrototype or ClientBundle (a nested bundle, which is how a big application groups icons by feature). The generator rejects anything else, see the pitfalls table.

  • ResourcePrototype.getName() returns the name of the accessor method; it is also how ClientBundleWithLookup finds a resource.

  • ClientBundleWithLookup adds getResource(String) and getResources(). Its Javadoc warns that calling these methods stops the compiler from pruning any resource of the bundle, so use it only when you really need name-based access (for example a tool-bar built from configuration).

public interface IconSet extends ClientBundleWithLookup {
  IconSet INSTANCE = GWT.create(IconSet.class);

  @Source("icon-star.png") ImageResource star();
  @Source("icon-warning.png") ImageResource warning();
  @Source("icon-next.png") ImageResource next();

  Nested nested();                       // a nested bundle: only a bundle type, no @Source

  interface Nested extends ClientBundle {
    @Source("badge.png") ImageResource badge();
  }
}

In the probe IconSet.INSTANCE.getResources().length was 3 (the three images; the nested bundle is not a resource of the outer one), getResource("star").getName() was star, getResource("nope") was null, and nested().badge().getWidth() was 16.

The resource types at a glance

Table 3. What each resource type is, and how its bytes reach the browser (default configuration)
Type You get Delivery in 2.13.1

ImageResource

getSafeUri(), getWidth(), getHeight(), getLeft(), getTop(), isAnimated()

Each image under about 48 KiB is a data: URL in the JavaScript; bigger ones, and those with preventInlining, become <hash>.cache.png files. With ClientBundle.enableInlining off, images are composited into image strips.

DataResource

getSafeUri() (and the deprecated getUrl())

A data: URL under the size limit, otherwise a <hash>.cache.<ext> file. @MimeType sets the type, @DoNotEmbed forces a file.

TextResource

getText()

The text is a string constant inside the JavaScript (read as UTF-8 at compile time); synchronous.

ExternalTextResource

getText(ResourceCallback<TextResource>)

All external texts of the bundle go into one JSON file, <hash>.cache.txt, fetched on first use and cached; asynchronous.

CssResource (also with .gss files)

ensureInjected(), getText(), class-name accessors

A string of obfuscated CSS inside the JavaScript, injected when you call ensureInjected(). Covered in depth on Styling: CSS & GSS.

GwtCreateResource<T>

create() returns a new T

No file: a factory around GWT.create.

a nested ClientBundle

the nested bundle

Its own generated class.

The Developer Guide names GssResource in its GSS pages, but there is no such type in 2.13.1: GSS is a mode of CssResource (a .gss file in a CssResource accessor, with CssResource.enableGss on).

ImageResource

An ImageResource is the bundle’s image type. It is deliberately not tied to a widget: it is a description of an image (a URL, a size and a position inside that URL) that widgets, cells, renderers and CSS rules can all use.

AppResources res = AppResources.INSTANCE;

Image logo = new Image(res.logo());           // widget
logo.setResource(res.logo());                 // swap later
SafeHtml html = new ImageResourceRenderer().render(res.star());   // for cells and SafeHtml builders
AbstractImagePrototype.create(res.star()).applyTo(existingImage); // bridge for old APIs

Image(ImageResource) and Image.setResource (the Image widget is described on Widgets) set the width, height and (for strips) the clipping for you. ImageResourceRenderer renders to SafeHtml, ImageResourceCell renders in the cell widgets (see Cell Widgets), and UiBinder declares one with <ui:image field="logo" src="logo.png"/> (see UiBinder). getURL() is deprecated; use getSafeUri(), which returns a SafeUri that can be passed to Anchor.setHref(SafeUri) and similar methods without an unchecked string.

@ImageOptions

@ImageOptions on an accessor tunes the compile-time processing of one image:

Table 4. The attributes of @ImageOptions (2.13.1)
Attribute Default Effect

flipRtl

false

The image is mirrored about the vertical axis when LocaleInfo.isRTL() is true (arrows, disclosure indicators). The generator renders a second, mirrored image and picks between them with an isRTL() test at run time.

width, height

-1

Scale the image at compile time to this size. If only one is set, the aspect ratio is kept.

repeatStyle

RepeatStyle.None

Horizontal, Vertical or Both marks an image that a CSS rule will tile; it changes how the image is arranged in a strip and which repeat-x/repeat-y the sprite rule gets. Both also keeps the image out of strips.

preventInlining

false

Always serve the image as a separate file instead of a data: URL. For rarely shown images.

@Source("icon-next.png")
@ImageOptions(flipRtl = true)
ImageResource next();

@Source("cover-default.png")        // a 64 by 96 pixel file
@ImageOptions(width = 32)           // becomes 32 by 48 at compile time
ImageResource thumbnail();

@Source("stripe.png")
@ImageOptions(repeatStyle = RepeatStyle.Horizontal)
ImageResource stripe();

@Source("icon-star.png")
@ImageOptions(preventInlining = true)
ImageResource starFile();

In the probe, thumbnail().getWidth() and getHeight() returned 32 and 48, starFile().getSafeUri() returned http://127.0.0.1:8912/bookshelf/88D0F514D62A5124E8D7C3C870FE10B9.cache.png while star() was a data URL, and in a second build with inlining off and the extra locale Arabic (ar, an RTL locale), LocaleInfo.getCurrentLocale().isRTL() was true and the strip served for next() was a different file (the RTL strip) from the one served for en. Mirroring only matters for locales that are RTL; if the module has none, flipRtl costs a second image that is never used.

Sprites or inlining: what the compiler really does

The Developer Guide and its Image Bundles page present ImageResource as the type through which all the images of an interface are combined into one optimized image. In 2.13.1 that is only half of the story, because the default configuration prefers inlining. The module Resources.gwt.xml defines the deferred-binding property ClientBundle.enableInlining (values true,false) and sets it to true; the inlining generator then treats every image as a stand-alone file and, when the file is small enough, embeds it as a base64 data: URL instead of building a strip. Setting the property to false brings back the classic image strips. The probe measured both configurations with the same bundle:

enableInlining = true (default), locale en
logo:      160x48  left=0   top=0   url=data:image/png;base64,iVBORw0K... (len 1266)
star:       16x16  left=0   top=0   url=data:image/png;base64,iVBORw0K... (len 222)
thumbnail:  32x48  left=0   top=0   url=data:image/png;base64,iVBORw0K... (len 810)
banner:    220x220 left=0   top=0   url=http://127.0.0.1:8912/bookshelf/C8E1F467097791503C560E209E8A... (145,604 bytes)
starFile:   16x16  left=0   top=0   url=http://127.0.0.1:8912/bookshelf/88D0F514D62A5124E8D7C3C870FE...

enableInlining = false, locale en
logo:      160x48  left=220 top=0   url=http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92E...
thumbnail:  32x48  left=220 top=48  url=http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92E...
warning:    16x16  left=220 top=96  url=http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92E...
star:       16x16  left=220 top=112 url=http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92E...
next:       16x16  left=220 top=128 url=http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92E...
banner:    220x220 left=0   top=0   url=http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92E...
stripe:      8x24  left=0   top=0   url=http://127.0.0.1:8913/bookshelf/BB9E1A9C36A001C52218A276672C...

With inlining on, getLeft() and getTop() are always 0: nothing is composited. With inlining off, the images share one strip (88F667...cache.png, 169,698 bytes in the probe, mostly the noisy banner) and each ImageResource knows its rectangle (left=220 top=112 for the star); the horizontally repeating stripe is arranged in a strip of its own, and the preventInlining star is a separate file.

flowchart TD A["ImageResource accessor"] --> B{"preventInlining or RepeatStyle.Both"} B -- "yes" --> X["separate hash.cache.png, never in a strip"] B -- "no" --> C{"ClientBundle.enableInlining"} C -- "true, the default" --> D{"file under the data URL limit"} D -- "yes" --> E["data: URL inside the permutation JavaScript"] D -- "no" --> X C -- "false" --> F["composited into an image strip, one strip per RepeatStyle"] F --> G["hash.cache.png plus left and top for each image"]

The DOM shows what the widget does with each result. new Image(res.star()) in the default configuration is a plain <img> with the data URL as its source:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9h..." width="16" height="16" class="gwt-Image">

With inlining off the same widget is a transparent one-pixel GIF (clear.cache.gif, shipped by GWT) whose CSS background selects the rectangle of the strip:

<img src="http://127.0.0.1:8913/bookshelf/clear.cache.gif"
     style="width:16px;height:16px;background:url(http://127.0.0.1:8913/bookshelf/88F667C0B902BBFDBC9DFB2FE92ED2DC.cache.png) no-repeat -220px -112px;"
     border="0" class="gwt-Image">

Widgets that build images through AbstractImagePrototype use the second form even with inlining on. The stock Tree with a bundle of its own (shown in the migration section) rendered each icon as an img with clear.cache.gif as its source and the data URL in the CSS background.

Which setting is better? Inlining removes the request entirely, but the bytes are base64 (about a third bigger) and are repeated in the JavaScript of every permutation (a browser downloads only its own permutation, but that file grows); strips need one extra request but keep the script small and let the image be cached on its own. The Developer Guide’s argument for strips (browsers allow only two connections per host) describes HTTP/1.1 browsers of its time; modern browsers allow more and HTTP/2 multiplexes requests, so the gain from spriting is smaller today than the guide suggests. Measure with your own bundle before changing the default.

Sprites in CSS

A CSS rule can use an ImageResource of the same bundle as its background, with the width and height filled in for you. In the legacy CSS syntax this is the @sprite rule (@sprite .rating { gwt-image: "star"; }); in GSS it is the gwt-sprite property, which is replaced by the properties needed to show the image:

.rating {
  gwt-sprite: "star";
  display: inline-block;
}

.shelfBar {
  gwt-sprite: "stripe";    /* stripe has RepeatStyle.Horizontal */
}

The compiler turned the first rule into this CSS (the data URL is shortened here):

.CFEWP3D-a-d{height:16px;width:16px;overflow:hidden;background:url(data:image/png;base64,iVBORw0K...) -0px -0px  no-repeat;display:inline-block}
.CFEWP3D-a-e{height:24px;overflow:hidden;background:url(data:image/png;base64,iVBORw0K...) -0px -0px  repeat-x}

Note that the horizontally repeating sprite gets a height but no width, and repeat-x; a vertically repeating one would get a width and repeat-y. With inlining off the same rules reference the strip’s URL and the negative offsets of the image inside it. The accessor String rating(); on the CssResource interface returned the obfuscated name CFEWP3D-a-d in that build (obfuscated names change from build to build); in Super Dev Mode the same accessor returned the readable com-example-bookshelf-client-resources-AppCss-rating. The data-resource twin of sprites is @url in CSS and resourceUrl("accessorName") in GSS: both produce a url(...) from a DataResource or ImageResource accessor of the same bundle. The full syntax of these directives is on Styling: CSS & GSS.

Supported image formats

The generator reads images with Java’s ImageIO framework at compile time, and the default extensions are PNG, JPEG, GIF and BMP. Animated GIFs are accepted but are never put in a strip (isAnimated() is true); they are still served efficiently by the bundle machinery. SVG is not an image format for ImageResource: pointing an ImageResource at an .svg file stops the compile with Unrecognized image file format. Declare SVG files as DataResource with @MimeType("image/svg+xml") (they then get a data: or strong-named URL you can use in <img src> or CSS), or inline the markup through a TextResource and SafeHtml when the file is trusted build-time content. The compiler also decodes every image it bundles, so only commit images you trust (the legacy ImageBundle Javadoc warns about a JVM vulnerability in the ImageIO classes; keep the JDK current).

DataResource

A DataResource is the simplest resource: a URL by which the contents of a file can be fetched. Use it for anything that is not an image or text the compiler should understand: a spreadsheet template, a PDF, a font, a custom cursor, an SVG or an audio clip.

@Source("catalogue-template.csv")
@MimeType("text/csv")
DataResource template();

@Source("catalogue-template.csv")
@DoNotEmbed
DataResource templateFile();

@Source("export-sample.csv")
DataResource sample();
Anchor template = new Anchor("CSV template");
template.setHref(res.template().getSafeUri());      // data: URL, the file has 65 bytes
template.getElement().setAttribute("download", "catalogue-template.csv");

The three accessors were delivered like this:

template.url     = data:text/csv;base64,dGl0bGUsYXV0aG9yLHllYXIsY29waWVzCiJFeGFtcGxlIFRpdGxlIiwiRXhhbXBsZSBBdXRob3IiLDIwMDEsMwo=
templateFile.url = http://127.0.0.1:8912/bookshelf/8D8BD1DF43E414CEF7E20BF9BA7F33E7.cache.csv
sample.url       = http://127.0.0.1:8912/bookshelf/F5C30E10877F8DB1884AB44A9EB454DC.cache.csv
  • @MimeType sets the type used in the data: URL. Without it the generator asks the file connection for its content type (the JDK guesses from the extension); set it explicitly for types the JDK does not know.

  • @DoNotEmbed forces a file even for a tiny resource. Use it for browsers or plug-ins that cannot read RFC 2397 data: URLs, or when the resource is fetched by code that cannot use them. templateFile() above did exactly that, and the URL it returned is the strong-named copy.

  • The size limit. The inlining generator embeds a resource only when its bytes are fewer than 65,536 (MAX_INLINE_SIZE = 2 << 15) and the encoded string is shorter than 65,535 characters (MAX_ENCODED_SIZE). Base64 inflates data by a third, so the real limit is about 48 KiB of raw file. The 141,934-byte export-sample.csv was therefore a file.

  • Identical content is stored once. The strong name is a hash of the bytes (Murmur3, upper-case hex), so two accessors that point at the same content share one output file. With inlining off, template() and templateFile() returned the same 8D8BD1DF...cache.csv URL.

  • getUrl() is deprecated; use getSafeUri().asString() for a string and pass the SafeUri itself where an API accepts it.

TextResource and ExternalTextResource

Both types give access to static text, such as a licence, a help page, an e-mail template or a JSON schema. The difference is when the text arrives.

TextResource: part of the script

TextResource.getText() returns a string that was written into the compiled JavaScript as a constant, so it is available synchronously and costs no request:

@Source("about.txt")
TextResource about();
Button about = new Button("About");
about.addClickHandler(e -> Window.alert(res.about().getText()));

The text is read as UTF-8 at compile time. The compiled permutation shows the literal in place of the file:

alert_0(($clinit_AppResources_safari_en_InlineClientBundleGenerator$aboutInitializer() ,
    $clinit_AppResources_safari_en_InlineClientBundleGenerator() ,
    'Bookshelf 1.0\nA small catalogue of books, authors, reviews and loans.\n'));

Because the text becomes part of every permutation, a large help document in a TextResource makes every download bigger; that is what the external variant is for. The text is data: show it with setText, setInnerText or Label, not setHTML. If the file really contains HTML that you wrote and review, convert it once with SafeHtmlUtils.fromTrustedString at one reviewed place, and never mix user-supplied values into it.

ExternalTextResource: one file, fetched on demand

An ExternalTextResource is identical in meaning but not in delivery: the text is not in the script. All external texts of one bundle are collected into a single JSON file with a strong name, fetched the first time any of them is requested, and cached for the rest of the page’s life:

@Source("changelog.txt")
ExternalTextResource changelog();
try {
  res.changelog().getText(new ResourceCallback<TextResource>() {   // one request, then cached
    @Override public void onError(ResourceException e) { Window.alert("Changelog unavailable: " + e.getMessage()); }
    @Override public void onSuccess(TextResource text)  { Window.alert(text.getText()); }
  });
} catch (ResourceException e) {                                   // the request could not even be started
  Window.alert("Changelog request failed: " + e.getMessage());
}

The bundle file in the output directory was 80A51388D2B8B1A22EB4D1964373A575.cache.txt, 49 bytes, and contained a JSON array holding the (only) text:

[
"1.0  first release\n1.1  loans and reviews\n"]

Points to know:

  • Two ways to fetch. By default the file is fetched with an XMLHttpRequest (so it must be same-origin). The configuration property ExternalTextResource.useJsonp (default false) switches to JSONP for a bundle served from another host. getText throws ResourceException when the request cannot be initiated, and onError receives a ResourceException if the payload cannot be evaluated.

  • It evaluates the response with eval. The prototype checks the payload against the JSON-safety regular expression from RFC 4627 and then calls eval. Under a Content-Security-Policy without 'unsafe-eval' the call does not work. The probe ran the application under default-src 'self'; script-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline': the application itself, the CSS injection and every data: image worked, but the changelog callback was never invoked, neither onSuccess nor onError. Adding 'unsafe-eval' to script-src made it work. In a strict-CSP application use a TextResource, a DataResource fetched with RequestBuilder and parsed by your own code, or a normal RPC or REST call.

  • The data URL rule does not apply: external text is always a file.

CssResource and GSS

A CssResource accessor turns a stylesheet into a typed interface whose methods return the (obfuscated) class names and constants of the file. The bundle holds the stylesheet text, and ensureInjected() puts it in a <style> element once:

public interface AppCss extends CssResource {
  String header();
  String nav();
  String lowStock();
  String rating();
}
AppResources.INSTANCE.css().ensureInjected();   // safe to call many times; usually first thing in onModuleLoad

Everything about the language belongs to Styling: CSS & GSS; this page only needs the packaging facts:

  • The stylesheet is a string inside the JavaScript, not a file, so it is never a separate request and it is always as fresh as the script.

  • The same accessor takes a .css file (the legacy syntax) or a .gss file (Google Closure Stylesheets). GSS needs <set-configuration-property name="CssResource.enableGss" value="true"/> in the module; with it off the compiler stops with GSS is not enabled. Add the following line to your gwt.xml file to enable it. The related properties CssResource.conversionMode (strict, lenient, off, default off) and CssResource.gssDefaultInUiBinder are described on Styling: CSS & GSS.

  • Class names in the stylesheet are strict by default: every class selector needs a String accessor on the interface, or the compile fails (see the pitfalls table).

  • The sprite and URL functions above (gwt-sprite, resourceUrl, @url, @sprite) are how a stylesheet reaches the bundle’s images and data files. Conditional rules such as @if (is("safari")) { ... } are evaluated per permutation (a probe stylesheet with a letter-spacing of 3px for Safari and 5px otherwise produced the 3px text only in the safari permutations and the 5px text only in gecko1_8).

GwtCreateResource

GwtCreateResource<T> is a bridge between a bundle and any type that GWT can instantiate: it is a factory that calls GWT.create for you. It is useful when a class should not know which implementation it gets, or when an interface wants to hand out several things that already exist in the module, such as a Messages interface.

GwtCreateResource<AppMessages> messages();                 // GWT.create(AppMessages.class)

@ClassType(ShortCitation.class)                            // create the subtype, expose the interface
GwtCreateResource<BookFormatter> formatter();
public interface BookFormatter { String format(Book book); }

public class ShortCitation implements BookFormatter {      // default-instantiable
  @Override public String format(Book book) {
    return book.getAuthor() + " (" + book.getYear() + "): " + book.getTitle();
  }
}

// elsewhere:
BookFormatter formatter = AppResources.INSTANCE.formatter().create();
String line = formatter.format(book);                       // "Frank Herbert (1965): Dune"

Each create() call returns a new instance. No deferred-binding rule is needed for ShortCitation, as long as it has a public no-argument constructor. @ClassType names the class passed to GWT.create; without it the type argument is used.

The Javadoc of @ClassType suggests using it for RPC, as @ClassType(Service.class) GwtCreateResource<ServiceAsync>. That pattern does not compile in 2.13.1: the generator writes a create() method whose return type is the @ClassType class (CatalogService), which is not assignable to CatalogServiceAsync, and the compile stops with The return type is incompatible with GwtCreateResource<CatalogServiceAsync>.create(). Use a subtype of the type argument in @ClassType, as above, and create RPC proxies with GWT.create(CatalogService.class) directly.

Locale-specific resources

ClientBundle is integrated with GWT internationalization. For each value of the deferred-binding property locale, the generator looks for variations of the file name in the manner of java.util.ResourceBundle, best match first. For @Source("default.txt") and the locale fr_FR it tries default_fr_FR.txt, default_fr.txt and default.txt; the locale’s segments are removed from the right one by one. This works for every resource type, so you can ship ownersManual_en.pdf and ownersManual_fr.pdf behind one accessor.

The Bookshelf module declares the locales en and es (plus the implicit default). Next to logo.png and about.txt it has logo_es.png and about_es.txt:

src/main/resources/com/example/bookshelf/client/resources/
    logo.png        logo_es.png
    about.txt       about_es.txt

Loading the page with ?locale=es in the probe returned a different logo (the data URL was 1,274 characters instead of 1,266) and the Spanish text:

locale=en  about=Bookshelf 1.0 | A small catalogue of books, authors, reviews and loans.
locale=es  about=Libro de estanteria 1.0 | Un pequeno catalogo de libros, autores, resenas y prestamos.
locale=default  (no locale parameter, no meta tag) falls back to the unsuffixed files

Things to remember:

  • The lookup is done at compile time, per locale. Each locale permutation contains only its own variant, so English users never download the Spanish logo.

  • The file name suffix must match the locale value of the module, not the browser’s language: if the module declares only es, a file logo_es_MX.png is never used.

  • Locale permutations multiply the work and the output: three locales and two user agents produced six permutations of about 288 KB each in the probe. That is the same price you already pay for Messages.

  • A missing variant is silently replaced by the base file; there is no warning when logo_es.png is forgotten.

See Internationalization for the locale property, locale.queryparam and <extend-property>.

User-agent-specific resources

There is no _safari suffix for file names: the file lookup knows only the locale property. Two mechanisms cover browser differences.

Inside a stylesheet. CSS and GSS conditionals can test the user.agent property, and the choice is made at compile time (no runtime cost). In GSS it is @if (is("safari")) { ... } @else { ... }; in the legacy CSS syntax @if user.agent safari { ... }. The probe confirmed that each permutation keeps only the matching branch.

A different file for a different engine. Choose between two bundles with ordinary deferred binding. A <replace-with> rule cannot name a bundle interface as its replacement: the compile stops with Rebind result 'com.example.bookshelf.client.resources.BadgeResourcesWebkit' must be a class. Put the rule on a small factory class that deferred binding replaces, and let each factory create its own bundle:

public interface BadgeResources extends ClientBundle {
  BadgeResources INSTANCE = GWT.<Factory>create(Factory.class).create();

  @Source("badge.png")
  ImageResource badge();

  class Factory {                                   // default behaviour
    BadgeResources create() { return GWT.create(BadgeResources.class); }
  }
}

public interface BadgeResourcesWebkit extends BadgeResources {
  @Override @Source("badge_webkit.png")
  ImageResource badge();

  class Factory extends BadgeResources.Factory {    // chosen for user.agent safari
    @Override BadgeResources create() { return GWT.create(BadgeResourcesWebkit.class); }
  }
}
<replace-with class="com.example.bookshelf.client.resources.BadgeResourcesWebkit.Factory">
  <when-type-is class="com.example.bookshelf.client.resources.BadgeResources.Factory" />
  <when-property-is name="user.agent" value="safari" />
</replace-with>

In headless Chrome, which selected the safari permutation, BadgeResources.INSTANCE.badge() returned the 214-character data URL of badge_webkit.png. The compiled safari permutations contain BadgeResourcesWebkit_en_InlineClientBundleGenerator, the gecko1_8 ones contain BadgeResources_en_InlineClientBundleGenerator and no Webkit class at all. GWT 2.13.1 defines only two values for the property, gecko1_8 and safari (UserAgent.gwt.xml), so a user-agent split gives each locale exactly two permutations; use it only for a real rendering difference. Many "browser-specific image" needs are better served by CSS media queries and image-set().

The knobs: deferred-binding and configuration properties

Resources.gwt.xml defines these properties that affect bundles (all verified in the 2.13.1 file):

Table 5. ClientBundle properties
Property Default Effect

ClientBundle.enableInlining (deferred binding)

true

true embeds small resources as data: URLs; false writes files and builds image strips.

ClientBundle.enableRenaming (configuration)

true

true gives files strong names (<hash>.cache.<ext>); false keeps the original names.

ExternalTextResource.useJsonp (configuration)

false

Fetch the external text bundle with JSONP instead of XHR.

CssResource.enableGss, CssResource.conversionMode, CssResource.gssDefaultInUiBinder, CssResource.style, CssResource.obfuscationPrefix, CssResource.mergeEnabled, CssResource.allowedAtRules, CssResource.allowedFunctions, CssResource.reservedClassPrefixes

see Styling: CSS & GSS

CSS and GSS handling.

Set them in the module file: <set-property name="ClientBundle.enableInlining" value="false"/> and <set-configuration-property name="ClientBundle.enableRenaming" value="false"/>. Turning renaming off removes the cache-forever property: the probe’s war directory then held banner.png, export-sample.csv and catalogue-template.csv under their original names, the external-text bundle as com_example_bookshelf_client_resources_AppResources_jsonbundle.txt, and three temporary-named images like ImageResourceGenerator17549398938070882451.png. Two bundles with a same-named file would then collide, and the files can no longer be given a far-future expiry; leave renaming on.

The source tree still contains an MHTML-based generator from the Internet Explorer era, but 2.13.1’s Resources.gwt.xml binds only the static and the inline generators.

Caching: what is written and how to serve it

The files in the war directory

After mvn package the module output directory of the six-permutation probe held (sizes in bytes; ls -l columns trimmed):

    288399  4A0539B63BAAA942B515B6077F5B15AE.cache.js     locale es, user.agent safari
    288738  59C0932D131152DAEA4B11E6D5FFFA93.cache.js     locale es, user.agent gecko1_8
    288299  8CAFC011635EE41CB5E435F33C3AC410.cache.js     locale en, user.agent safari
    288658  C1674B6986147ACB5C987C1047311F24.cache.js     locale default, user.agent gecko1_8
    288638  C24C938499B73830E2AFCD752F530B14.cache.js     locale en, user.agent gecko1_8
    288319  CACD996127D36DF0EC42B9A8B65E791C.cache.js     locale default, user.agent safari
        49  80A51388D2B8B1A22EB4D1964373A575.cache.txt    ExternalTextResource bundle
       150  88D0F514D62A5124E8D7C3C870FE10B9.cache.png    starFile (preventInlining)
        65  8D8BD1DF43E414CEF7E20BF9BA7F33E7.cache.csv    templateFile (DoNotEmbed)
    145604  C8E1F467097791503C560E209E8A3251.cache.png    banner (over the limit)
    141934  F5C30E10877F8DB1884AB44A9EB454DC.cache.csv    sample (over the limit)
      1415  85B004BBB10BADC9964AD6DE9572205E.gwt.rpc      RPC serialization policy
       933  CC3ED00F7EAF7D45F411FC9A39408C4A.gwt.rpc      RPC serialization policy
        43  clear.cache.gif                               shipped by GWT, used for image strips
      7847  bookshelf.nocache.js                          bootstrap, revalidate
     14281  bookshelf.devmode.js
       460  compilation-mappings.txt                      permutation to locale and user.agent

Every file whose name contains .cache. is content-addressed: its name is a hash of its bytes, so it can never change without changing its URL. The compiled permutations follow the same rule, and bookshelf.nocache.js is the only file the browser must always ask for. The generated JavaScript refers to the resource files through the module base URL. Compiled with -style PRETTY (add -Dgwt.style=PRETTY to the Maven command) the bundle’s static fields read:

externalImage = $moduleBase + 'C8E1F467097791503C560E209E8A3251.cache.png';
externalImage3 = $moduleBase + '88D0F514D62A5124E8D7C3C870FE10B9.cache.png';
externalTextUrl = $moduleBase + '80A51388D2B8B1A22EB4D1964373A575.cache.txt';
...
sample = new DataResourcePrototype(($clinit_UriUtils() , new SafeUriString($moduleBase + 'F5C30E10877F8DB1884AB44A9EB454DC.cache.csv')));
templateFile = new DataResourcePrototype(($clinit_UriUtils() , new SafeUriString($moduleBase + '8D8BD1DF43E414CEF7E20BF9BA7F33E7.cache.csv')));
template = new DataResourcePrototype(($clinit_UriUtils() , new SafeUriString('data:text/csv;base64,dGl0bGUsYXV0aG9yLHllYXIs...')));

Telling the web server

GWT writes the files but does not set HTTP headers; that is the job of whatever serves the war. The Developer Guide asks for the *.cache.* glob to be served with publicly cacheable headers and a far-future Expires. In the Bookshelf the servlet container serves the compiled application next to the RPC endpoint, so a small Jakarta filter does it (the pattern .cache. does not match .nocache., which has no dot before cache):

public class CacheHeadersFilter implements Filter {
  @Override
  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
      throws IOException, ServletException {
    String path = ((HttpServletRequest) req).getRequestURI();
    HttpServletResponse response = (HttpServletResponse) resp;
    if (path.contains(".nocache.")) {
      response.setHeader("Cache-Control", "no-cache");          // always revalidate the bootstrap
    } else if (path.contains(".cache.")) {
      response.setHeader("Cache-Control", "public, max-age=31536000, immutable");
    }
    chain.doFilter(req, resp);
  }
}
<filter>
  <filter-name>cacheHeaders</filter-name>
  <filter-class>com.example.bookshelf.server.CacheHeadersFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>cacheHeaders</filter-name>
  <url-pattern>/bookshelf/*</url-pattern>
</filter-mapping>

Jetty 12.1.10 served the war with these headers (trimmed):

$ curl -sI http://127.0.0.1:8912/bookshelf/88D0F514D62A5124E8D7C3C870FE10B9.cache.png
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutable
Content-Type: image/png
Content-Length: 150

$ curl -sI http://127.0.0.1:8912/bookshelf/bookshelf.nocache.js
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: text/javascript

immutable tells browsers that support it not to revalidate even on reload, which is correct for hash-named files. If you put a CDN or reverse proxy in front, give it the same two rules by path pattern. In Super Dev Mode the code server on port 9912 serves the same strong names without cache headers, which is fine for development.

Cookies on static files break shared caching

The first version of the Bookshelf filter chain mapped the session filter (the one that makes sure an XSRF token can be derived from the session) to /*. Every static file then answered with a Set-Cookie: JSESSIONID=... header, and Jetty added Expires: Thu, 01 Jan 1970 00:00:00 GMT next to the filter’s Cache-Control. Shared caches generally do not store responses that set cookies, and the contradictory Expires is noise at best. Mapping the session filter only to the RPC and REST paths (/bookshelf/catalog, /bookshelf/xsrf, /api/*) removed both headers from the static responses; keep the paths that deliver *.cache.* files free of sessions, cookies and authentication.

Worked example: the Bookshelf header strip

The Bookshelf shell needs a header with the logo (different for Spanish), a star rating icon in a CSS rule, a link to download a CSV template, an "About" text and a "Changelog" that is rarely opened. One bundle serves all of it.

public interface AppResources extends ClientBundle {
  AppResources INSTANCE = GWT.create(AppResources.class);

  @Source("app.gss") AppCss css();                          // stylesheet, inside the script

  @Source("logo.png") ImageResource logo();                 // logo_es.png in the es permutation
  @Source("icon-star.png") ImageResource star();            // small: data URL
  @Source("icon-next.png") @ImageOptions(flipRtl = true) ImageResource next();
  @Source("cover-default.png") @ImageOptions(width = 32) ImageResource thumbnail();
  @Source("banner.png") ImageResource banner();             // 145,604 bytes: a cache file

  @Source("catalogue-template.csv") @MimeType("text/csv") DataResource template();
  @Source("export-sample.csv") DataResource sample();       // 141,934 bytes: a cache file

  @Source("about.txt") TextResource about();                // about_es.txt for es
  @Source("changelog.txt") ExternalTextResource changelog();

  GwtCreateResource<AppMessages> messages();
  @ClassType(ShortCitation.class) GwtCreateResource<BookFormatter> formatter();
}
/* app.gss, the rules that use the bundle */
.rating {
  gwt-sprite: "star";
  display: inline-block;
}
public class BookToolbar extends Composite {
  public BookToolbar() {
    AppResources res = AppResources.INSTANCE;
    res.css().ensureInjected();

    FlowPanel panel = new FlowPanel();
    panel.add(new Image(res.logo()));

    InlineLabel stars = new InlineLabel();
    stars.setStyleName(res.css().rating());
    panel.add(stars);

    Anchor template = new Anchor("CSV template");
    template.setHref(res.template().getSafeUri());
    template.getElement().setAttribute("download", "catalogue-template.csv");
    panel.add(template);

    Button about = new Button("About");
    about.addClickHandler(e -> Window.alert(res.about().getText()));
    panel.add(about);

    Button changes = new Button("Changelog");
    changes.addClickHandler(e -> showChangelog(res));       // ExternalTextResource, see above
    panel.add(changes);

    initWidget(panel);
  }
}

The DOM that headless Chrome produced for ?locale=en and ?locale=es (data URLs shortened):

<img src="data:image/png;base64,iVBORw0KGgoA..." width="160" height="48" class="gwt-Image">
<span class="CFEWP3D-a-d"></span>   <!-- obfuscated name, differs per build -->
<a class="gwt-Anchor" href="data:text/csv;base64,dGl0bGUsYXV0..." download="catalogue-template.csv">CSV template</a>
<button type="button" class="gwt-Button">About</button>
<button type="button" class="gwt-Button">Changelog</button>

Where each byte went:

Accessor Size Delivery

logo() and the icons

1 KB or less

data: URL in the permutation JavaScript, locale variant chosen at compile time

.rating sprite

16 by 16

The same star as a CSS background, data URL inside the injected stylesheet

template()

65 bytes

data:text/csv;base64,... in the link’s href

banner(), sample()

145,604 and 141,934 bytes

<hash>.cache.png and <hash>.cache.csv, cached for a year by CacheHeadersFilter

about()

70 bytes

A string in the script

changelog()

42 bytes

80A51388....cache.txt, one request when the button is first pressed

The covers of real books are not in the bundle: the catalogue returns a URL per book, and a view builds new Image(SafeUri) from it, showing res.thumbnail() only while a book has no cover. That keeps the bundle limited to assets the build knows.

Migrating from ImageBundle

ImageBundle is the 2007 mechanism (GWT 1.4) that the book teaches in its widgets chapter: an interface extending ImageBundle, methods returning AbstractImagePrototype, and images named by the method name or by a resource annotation. The book writes the file name in a doc-comment tag; the ImageBundle Javadoc says that form was superseded by the @Resource annotation, and neither is used by current code. ClientBundle arrived in GWT 2.0.0 as a generalisation of the idea, and in 2.13.1 the old interface is still in the jar but marked @Deprecated (its Javadoc says it is replaced by ClientBundle and ImageResource).

It still works. To check, the probe compiled a legacy bundle with the 2.13.1 compiler on JDK 21:

@SuppressWarnings("deprecation")
public interface ToolBarImageBundle extends ImageBundle {
  @Resource("com/example/bookshelf/client/resources/icon-star.png")
  AbstractImagePrototype star();
}

It compiled without a GWT warning (javac reports the deprecation only), a generated class ToolBarImageBundle_generatedBundle appeared, and createImage() produced an image whose URL was http://127.0.0.1:8912/bookshelf/88D0F514D62A5124E8D7C3C870FE...cache.png: for this bundle the legacy generator wrote a strong-named file and did not inline (the file has the same hash as the preventInlining star, so both share it). Plan to migrate, because nothing improves the legacy path any more.

Table 6. ImageBundle to ClientBundle
ImageBundle ClientBundle Note

interface Images extends ImageBundle

interface Images extends ClientBundle

Add Images INSTANCE = GWT.create(Images.class); if you want a singleton.

AbstractImagePrototype star();

ImageResource star();

@Resource("path/star.png")

@Source("path/star.png")

Paths are relative to the interface’s package, then classpath-absolute, as before.

the method name selects star.png, star.gif or star.jpg (in that order)

the method name selects star.png, .jpg, .gif, .bmp (in that order)

The order of .jpg and .gif differs; name the file with @Source when both exist.

images.star().createImage()

new Image(images.star())

Same widget; sets size and clipping.

prototype.applyTo(image)

image.setResource(images.star())

AbstractImagePrototype.create(imageResource) still wraps a resource when an old API needs a prototype.

images.star().getSafeHtml()

new ImageResourceRenderer().render(images.star())

Both return SafeHtml.

The widgets of the library followed the same road: Tree.Resources replaces TreeImages, MenuBar.Resources replaces MenuBarImages, and HorizontalSplitPanel.Resources replaces HorizontalSplitPanelImages; the old ImageBundle-based interfaces and constructors are @Deprecated. Restyling a stock widget is therefore a bundle that extends the widget’s:

public interface ShelfTreeResources extends Tree.Resources {
  @Override @Source("icon-next.png")    ImageResource treeClosed();
  @Override @Source("icon-star.png")    ImageResource treeLeaf();
  @Override @Source("icon-warning.png") ImageResource treeOpen();
}

Tree tree = new Tree(GWT.<ShelfTreeResources>create(ShelfTreeResources.class));

The accessor names must match the widget’s own (treeClosed, treeLeaf, treeOpen); the tree then draws its icons from your files.

Security notes

  • Treat text resources as data. getText() output goes to setText, setInnerText or Label. setHTML with a bundle text is only acceptable for markup that you wrote and review; wrap it once with SafeHtmlUtils.fromTrustedString and never concatenate user input into it.

  • SafeUri is trusted by construction. getSafeUri() returns a SafeUri that the generator created from compile-time content, so it may be assigned to href and src through the SafeUri overloads. Do not convert user-controlled strings to SafeUri to get the same effect.

  • A Content-Security-Policy must allow data: for images (img-src data:) if inlining stays on. Use @DoNotEmbed or preventInlining for resources a policy cannot allow as data:. ExternalTextResource over XHR needs 'unsafe-eval', as measured above.

  • Compile-time input is trusted input. The compiler decodes images and reads texts from the repository; do not bundle files from untrusted sources.

  • No secrets in bundles. Everything in a bundle is delivered to every browser, in clear, and a strong-named file is public to anyone who knows the name.

  • J2CL and the future. ClientBundle is a feature of the GWT compiler and its generators. The org.gwtproject.* modules published for J2CL (all still at 1.0.0 release-candidate versions on Maven Central) contain no replacement for it (see J2CL & the Future for their status); a J2CL migration needs another asset pipeline, for example a bundler.

Pitfalls

The messages below were printed by the 2.13.1 compiler for a probe module with one deliberately broken bundle per row, or taken from the 2.13.1 generator source where the row says so. Package names are shortened with ... where it helps. Most generator errors are followed by a generic second line that is the failure you will search for in the log: Failed to resolve 'com.example...BadMissing' via deferred binding, because the GWT.create of the bundle could not complete.

Symptom (message as printed) Cause Fix

Resource nope.png not found. Is the name specified as ClassLoader.getResource() would expect?

The @Source path is wrong, the file is not on the classpath (for example it is under src/main/java and not copied), or the case differs from the file name.

Put the file next to the interface under src/main/resources, or give an absolute classpath path. Check capitalisation: a case-insensitive file system hides a mismatch that a Linux build fails on.

No com.google.gwt.resources.client.ClientBundle$Source annotation and no resources found with default extensions

An accessor has no @Source and no file accessorName.<ext> exists in the bundle’s package.

Add @Source, or name the file after the method.

Exactly one image may be specified (ImageResource); Exactly one resource must be specified (DataResource and TextResource, from the generator source)

@Source lists two or more files.

Only CssResource merges several files. Declare one accessor per file.

Unable to implement text because it does not derive from com.google.gwt.resources.client.ResourcePrototype or com.google.gwt.resources.client.ClientBundle

An accessor returns String or another plain type.

Return TextResource and call getText().

The method text(String) in the type BadParam_es_InlineClientBundleGenerator is not applicable for the arguments (), then Rebind result '...BadParam_es_InlineClientBundleGenerator' could not be found

An accessor declares a parameter. The messages come from the generated class, in each locale.

Accessors take no arguments. Pass a name through ClientBundleWithLookup.getResource(String) if you need to look up by key.

Unrecognized image file format

An ImageResource points at an SVG (or another format ImageIO cannot read).

Use a DataResource for SVG.

The return type is incompatible with GwtCreateResource<CatalogServiceAsync>.create()

@ClassType(CatalogService.class) with GwtCreateResource<CatalogServiceAsync>, the pattern in the @ClassType Javadoc.

Use a subtype of the type argument in @ClassType, or call GWT.create directly for RPC.

Rebind result 'com.example.bookshelf.client.resources.BadgeResourcesWebkit' must be a class

A <replace-with> rule names a bundle interface as the replacement.

Replace a factory class and let it call GWT.create on the other bundle (above).

The following non-obfuscated class is present in a strict CssResource: engineBadge. Fix by adding String accessor method(s) to the CssResource interface for obfuscated classes, or use an @external declaration for unobfuscated classes.

The stylesheet has a class selector with no matching accessor on the CssResource interface.

Add String engineBadge();, or declare the class external.

GSS is not enabled. Add the following line to your gwt.xml file to enable it: <set-configuration-property name="CssResource.enableGss" value="true" />

A .gss file is used while CssResource.enableGss is off.

Set the property, or convert the file to CSS.

getText() of an ExternalTextResource never calls back

The page runs under a Content-Security-Policy without 'unsafe-eval'; the prototype evaluates the JSON with eval, which the browser blocks. Neither onSuccess nor onError fires.

Allow 'unsafe-eval', or use TextResource/DataResource.

An unused resource still appears in the war directory (C8E1F467...cache.png) after its last use was removed

The generator writes files while it builds the bundle; pruning removes only the JavaScript that refers to them.

Remove the accessor from the bundle (or the file from the classpath) to stop shipping the file.

Inlined images do not show under a Content-Security-Policy

The policy has no img-src data:, so the browser refuses the data: URLs.

Add data: to img-src, or set ClientBundle.enableInlining to false / use preventInlining.

Every static file carries Set-Cookie and Expires: Thu, 01 Jan 1970 00:00:00 GMT

A session-creating filter is mapped to /*, so the cache-forever files are served per session.

Map the session filter to the dynamic paths only.

The logo is the same in every locale although logo_es.png exists

The module does not declare the locale (<extend-property name="locale" values="es"/>), the file name does not match the locale value, or the permutation was not rebuilt.

Check compilation-mappings.txt for the locale and the suffix of the file.

getLeft() and getTop() are 0 for every image although the Developer Guide says images are combined

Inlining is on (the default), and inlined images are not composited.

Expected. Set ClientBundle.enableInlining to false if you want strips.

Recent changes and the outdated book

Table 7. ClientBundle and ImageBundle in the GWT release notes
Version Change

1.4

ImageBundle introduced for automatic spriting of images (as the 2.0.0 notes describe it).

2.0.0 (2009-12-09)

ClientBundle generalises the technique to text files, CSS and other resources, together with UiBinder.

2.5.0

Generator result caching for the ClientBundle generators makes development refresh much faster.

2.6.0

@url supports ImageResource; the user.agent values change.

2.8.0

GSS leaves experimental status and the legacy CSS syntax is deprecated; CssResource.enableGss turns GSS on.

2.8.1 to 2.13.1

The release notes mention no ClientBundle change. The 2.13.1 sources are the ones quoted on this page.

  • The book. Chapter 4 of the Manning book (GWT 1.3 and 1.4) shows ImageBundle as the way to reduce image requests. The idea is sound and survives in ClientBundle, but the type is @Deprecated, the doc-comment form of the resource tag it uses has been superseded, and the book predates data-URL inlining, strong-named files, CssResource, DataResource and the text resources.

  • The official page. The Developer Guide Image Bundles page now describes ImageResource, but its argument about two connections per host and its statement that images are combined into a single image do not match the 2.13.1 defaults (inlining on). The ClientBundle page still mentions Internet Explorer 6 in the sprite notes, and calls the GSS mode GssResource, a name that is not a type.

See also

References