Compile Reports & Optimization

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-09. It is build and tooling knowledge with one client part: the compile report and the JFR events are produced by the compiler on the developer or CI machine, while the lightweight metrics are JavaScript events emitted by the compiled client code in the visitor’s browser. Every number and output block below was produced on the Bookshelf scenario of this section (three locales, two user agents, one DataGrid and one GWT.runAsync split point) with a private copy of the scratch application. The application grew a little during the section, so the absolute sizes differ from those on the compiler page; compare figures only inside one table.

Measure first

Optimising without numbers usually shrinks the wrong thing. GWT gives three different instruments, and each answers a different question:

Instrument Question it answers Where it runs Cost

Compile report (-compileReport)

Which packages, classes and fragments make my JavaScript big, and why is this class in the initial download?

Compile time, written to the -extra directory

About 2 to 3 seconds more and several MB of files per permutation

JFR events (2.13)

Where does the compiler spend its time, how many permutations, fragments and AST nodes, how many bytes?

Compile time, in the compiler JVM

Only while a JDK Flight Recorder recording is active

Lightweight metrics (__gwtStatsEvent)

How long do bootstrap, module start, RPC calls and fragment downloads take for a real visitor?

Browser

Negligible; nothing happens if the host page defines no collector

flowchart LR measure["Measure
compile report, JFR,
lightweight metrics"] --> find["Find the cause
package breakdown,
dependency chain"] find --> change["Change one thing
flag, property,
code or split point"] change --> verify["Compile again
and compare bytes"] verify --> measure

The flags themselves are described in Compiler & Compilation; this page is about using them with evidence. Splitting code into fragments is covered by Code Splitting, and the bootstrap sequence whose events are measured below by Linkers & Bootstrap.

The compile report

Producing it

-compileReport ("Compile a report that tells the Story of Your Compile", default off) collects, for every permutation, the size contributed by each package and class and how the code is distributed over fragments, and writes a set of static HTML pages. The gwt-maven-plugin compile goal has no compileReport parameter, so pass the flag through compilerArgs. With the plugin the extras directory is target/extras, and the report ends up in target/extras/<module>/soycReport/compile-report/.

<plugin>
  <groupId>net.ltgt.gwt.maven</groupId>
  <artifactId>gwt-maven-plugin</artifactId>
  <configuration>
    <moduleName>com.example.bookshelf.App</moduleName>
    <moduleShortName>bookshelf</moduleShortName>
    <compilerArgs>
      <arg>-generateJsInteropExports</arg>
      <arg>-compileReport</arg>
    </compilerArgs>
  </configuration>
</plugin>

The run printed the location of the extras, and the report is not part of the war:

[INFO]    Compilation succeeded -- 12,164s
[INFO] Linking into .../bookshelf-client/target/bookshelf-client-1.0-SNAPSHOT/bookshelf; Writing extras to .../bookshelf-client/target/extras/bookshelf

With the command-line compiler the same flag works with -extra dir. The flags related to the report are:

Flag Effect Status

-compileReport

Standard report. -nocompileReport is the negative form.

Documented; -soyc is an older alias found in the source

-XsoycDetailed

Adds detailedStories<n>.xml.gz, which link Java snippets to JavaScript snippets (257,758 bytes for one permutation here). The Developer Guide says it is most useful when investigating specific snippets.

Experimental, not listed by the -help output

-XcompilerMetrics

Adds compilerMetrics.xml (669,650 bytes here) and the "Compiler Metrics" pages: build times of the module analysis, precompile and compile phases and the lists of source files, types and unreferenced types.

Experimental, not listed by the -help output

The -XsoycDetailed flag is a size-for-time trade: the documentation says it is "at the expense of compile time". In this application the difference was within the noise of the machine. Two more switches (-XenableJsonSoyc and -XdisableSoycHtml) exist in the 2.13.1 source as undocumented experimental flags and were not run here.

What a report contains

For one permutation (locale=en, user.agent=safari) the report directory held 102 files and 6.2 MB, and the extras directory 8.4 MB. A six-permutation compile with -XsoycDetailed wrote about 40 MB of extras.

soycReport/
  compile-report/
    index.html                       overview of all permutations
    SoycDashboard-0-index.html       sizes of permutation 0 and its split points
    CompilerMetrics-0-index.html     only with -XcompilerMetrics
    total-0-packageBreakdown.html    package breakdown of the whole program
    initial-0-packageBreakdown.html  ... of the initial download
    leftovers-0-codeTypeBreakdown.html
    fragment1-0-overallBreakdown.html
    splitStatus-0.html, leftoverStatus-0.html
    methodDependencies-initial-0.html (+ .js), methodDependencies-total-0.html, methodDependencies-sp1-0.html
  stories0.xml.gz       dependencies0.xml.gz      splitPoints0.xml.gz     (detailedStories0.xml.gz with -XsoycDetailed)

Open compile-report/index.html directly from disk. It lists the permutations with their property values ('locale' : 'en' , 'user.agent' : 'safari' and so on). Without -XcompilerMetrics the "Compiler Metrics" link under each permutation points to a page that was not written.

Overall sizes and split points

The permutation dashboard shows three numbers and the split points. For the Bookshelf permutation:

Item Bytes Meaning

Full code size

262,930

The whole compiled program

Initial download size

262,286

What is downloaded before the application starts

Left over code

11

Code that no split point owns exclusively

Split point 1, AdminActivity::start

633

Fragment 1, the GWT.runAsync of the admin area

The Developer Guide explains the relation: without code splitting the initial download equals the full code size. Here the admin area is a trivial panel, so the split point moves only 633 bytes (0.2 %) out of the initial download. The same 633 bytes can be seen in a JFR event further down. The numbers are formatted with the default locale of the JVM that ran the compiler: on the machine used here they read 262.930 for 262,930 and 66,2 % for 66.2 %. A report generated on a build server with another locale looks different.

The sizes are the compiled program; the permutation file <hash>.cache.js was 265,852 bytes because the linker adds its wrapper, and about 89,500 bytes after gzip -9. Serve the .cache.js files compressed: it matters more than most flags below.

Package and code type breakdown

The initial download of that permutation, by code type:

Code type Bytes Share

allOther (application and GWT library code)

156,997

66.2 %

jre (the JRE emulation, for example java.util.ArrayList)

42,556

18.0 %

gwtLang (GWT runtime, com.google.gwt.lang)

34,592

14.6 %

rpcGen (generated RPC serializers)

2,704

1.1 %

rpcUser

194

0.1 %

The report also states that 4,202 bytes are strings and that 21,041 bytes cannot be attributed to a package or code type. The largest packages of the initial download were:

Package Bytes Share

com.google.gwt.user.cellview.client

37,066

15.6 %

com.google.gwt.lang

34,190

14.4 %

com.google.gwt.user.client.ui

27,757

11.7 %

java.util

24,289

10.2 %

java.lang

11,248

4.7 %

com.google.gwt.dom.client

11,068

4.7 %

com.example.bookshelf.client.view

4,682

2.0 %

com.example.bookshelf.shared

1,972

0.8 %

Drilling into a package lists classes: the largest allOther classes were com.google.gwt.i18n.client.NumberFormat (6,510 bytes), AbstractCellTable (5,916) and HasDataPresenter (5,547), and in the jre group java.util.ArrayList (1,922), java.util.AbstractMap (1,838) and java.lang.Throwable (1,672). The reading is immediate: the Bookshelf’s own code is 2 % of the program and the DataGrid is 15 %. Cutting application code would be pointless; the levers are the widget choice, the locale machinery and the runtime flags of the next section.

Why is this class in the initial download?

The dependency pages answer "I expected this to be left out". The Developer Guide describes the procedure: from the overview open the permutation, follow the "Report" link under "Initial download size", open the package and then the class; the page shows a chain of calls back to the entry point. The same information is in dependencies<n>.xml.gz, where every method lists its called by entries. Following the first caller at every step for the sort-icon initializer of the grid gave:

DataGrid_Resources_en_InlineClientBundleGenerator$dataGridSortAscendingInitializer::$clinit
  <- AbstractHeaderOrFooterBuilder::AbstractHeaderOrFooterBuilder
  <- DefaultHeaderOrFooterBuilder::DefaultHeaderOrFooterBuilder
  <- AbstractCellTable::$init
  <- AbstractCellTable::AbstractCellTable
  <- DataGrid::DataGrid

The DataGrid constructor is the link to break: create the grid inside a split point and the whole chain, including the generated client bundle, leaves the initial download. The "Split point status" and "Leftover status" pages explain the opposite case, an item that is only in the leftover fragment because two split points need it (each page offers a "See why it’s not exclusive to s.p. #n" link). The mechanics of exclusive and leftover fragments belong to Code Splitting.

Comparing permutations and keeping the report private

The overview lists every permutation, so differences between user agents or locales can be compared one by one. The four use cases named by the Developer Guide are reducing the whole download, the initial fragment, specific fragments and comparing permutations.

The report contains class names, method names and, with -XsoycDetailed, JavaScript snippets mapped to Java. Keep it out of the public site. With the Maven plugin the extras folder is outside the war (a search of the war found no soyc or symbol-map file), and the same holds for -extra of the command-line compiler as long as that directory is not inside -war. Never point -extra at the web root.

Reducing code size

All figures below are the total bytes of the six .cache.js files (three locales times two user agents), compiled with JDK 21.

Change Total bytes Versus default Remarks

Default (-optimize 9, OBFUSCATED)

1,591,788

about 265 KB per permutation

-XnoclassMetadata

1,472,418

-7.5 %

getName() no longer returns real names

jre.checks.checkLevel=OPTIMIZED

1,571,739

-1.3 %

removes bounds, API and numeric checks

jre.checks.checkLevel=MINIMAL

1,554,579

-2.3 %

removes the type checks too

-XnoclassMetadata and MINIMAL

1,436,127

-9.8 %

-optimize 5

1,595,478

+0.2 %

no gain below 9 here

-optimize 0

3,558,305

+123.5 %

never ship

-draftCompile

3,558,321

+123.5 %

never ship

-style PRETTY

4,380,419

+175.2 %

readable, for debugging only

Class metadata: -XnoclassMetadata

By default GWT keeps metadata so that Class.getName() and getSimpleName() work (-X[no]classMetadata, default on). Turning it off saved 119,370 bytes over the six files. The price is visible at run time. A line that logs App.class.getName() + " / " + App.class.getSimpleName() printed, in a headless Chrome:

com.example.bookshelf.client.App / App         (default)
Class$17 / Class$17                            (with -XnoclassMetadata)

So do not use the flag in an application that logs, serializes or switches on class names. In the source the disabled mode names a class "Class$" plus an id.

Runtime checks: jre.checks.*

The JRE emulation contains checks that Java programs get from the JVM. They are controlled by properties defined in Preconditions.gwt.xml: jre.checks.checkLevel (NORMAL, the default, OPTIMIZED or MINIMAL) and the four finer switches jre.checks.bounds, jre.checks.api, jre.checks.numeric and jre.checks.type, each AUTO (follow the level), ENABLED or DISABLED. In InternalPreconditions the bounds, API and numeric checks are on only at NORMAL, and the type check at OPTIMIZED or higher. Set the level in the module or on the command line:

<!-- bookshelf-client/src/main/module.gwt.xml, release build only -->
<set-property name="jre.checks.checkLevel" value="MINIMAL" />
# same effect from the compiler arguments of a profile
-setProperty jre.checks.checkLevel=MINIMAL

The property is a deferred-binding property, so it does not add permutations when it has one value. The trade-off is correctness: with the checks gone an out-of-range list.get(10) or a failed cast is no longer reported the way Java reports it. Measure the gain first (2.3 % here) and try OPTIMIZED before MINIMAL. The old -XcheckCasts flag is deprecated in favour of jre.checks.checkLevel, as the -help text says.

Avoiding heavy JRE classes

The report’s jre group is the cost of Java library classes that you pull in. A single call chain using BigDecimal to compute a late fee, new BigDecimal("0.25").multiply(BigDecimal.valueOf(days)).setScale(2, RoundingMode.HALF_UP), made one permutation grow from 265,852 to 290,470 bytes (+24,618, +9.3 %); the report named java.math as 21,220 bytes (8.1 %). Computing the fee in whole cents with int removes all of it:

// shared: Bookshelf late fee in cents, no java.math needed
public static int lateFeeCents(int daysLate) {
  return 25 * Math.max(0, daysLate);
}

Other classes with the same effect are listed on the JRE emulation page; see JRE Emulation & Shared Code. Prefer plain int, double and String in shared code, and check the report’s jre list after adding a library.

Other levers

  • Optimisation level. Keep the default -optimize 9. Level 5 was already as large as 9 here (+0.2 %) and level 0 more than doubled the output.

  • Widgets. com.google.gwt.user.cellview.client was 15.6 % of the Bookshelf. A plain table in the first screen and the DataGrid in a lazily loaded area is a real saving.

  • Locales. Every locale adds a permutation (more build output, not more bytes per visitor); see Internationalization.

  • Split points. Code that the first screen does not need belongs behind GWT.runAsync; the compile report is the tool that proves it left the initial fragment.

  • Compression. The .cache.js of 265,852 bytes compressed to about 89,500 bytes with gzip -9, a 66 % reduction without touching code. See Server Integration & Deployment.

  • Output style. OBFUSCATED is the default; PRETTY was 2.75 times larger.

Reducing compile time

Technique Measured here Notes

Default six-permutation compile

11 to 17 seconds

the compiler page measured 12 seconds

-draftCompile

7 to 9.5 seconds

optimisation level 0 plus no clustering, no literal inlining and the PACKAGE namespace

One permutation (-setProperty locale=en -setProperty user.agent=safari)

5.1 to 7.7 seconds

produced 1 of 6 files

-compileReport

+2 to 3 seconds

the output files are identical to the default

-localWorkers 1, 3, 6

inconclusive

see below

The timings were taken on a machine shared with other builds (load average 8 to 12), so use them only for order of magnitude; the byte counts are exact and repeatable.

Fewer permutations

The cheapest saving is to compile fewer permutations while developing. Fixing the properties reduced six permutations to one:

mvn -B package -pl bookshelf-client -am -DskipTests -Dgwt.draftCompile=true
# or, with compilerArgs in a dev profile:
#   -setProperty locale=en  -setProperty user.agent=safari

The Maven user properties gwt.draftCompile, gwt.optimize and gwt.localWorkers exist on the compile goal.

-localWorkers is not just threads

-localWorkers count is the number of workers that compile permutations. The 2.13.1 source shows what a worker is: ThreadedPermutationWorkerFactory creates in-process threads, but caps them at the system property gwt.jjs.maxThreads, whose default is 1. Every further worker is launched by ExternalPermutationWorkerFactory as a separate JVM that inherits the JVM arguments of the compiler (-Xmx and -D options) unless gwt.jjs.javaArgs overrides them. Consequences:

  • with no -localWorkers the six permutations were compiled one after the other by "In-process PermutationWorker 0", as the JFR events show;

  • -localWorkers N needs memory for N compiler JVMs, not for one;

  • timing gains depend on the free cores, and on the loaded machine used here the runs with 1, 3 and 6 workers (9.8 to 13.5 seconds) were not distinguishable from the default.

-incremental and -validateOnly

-incremental compiles faster by reusing data from the previous compile, but it works on one permutation only:

[ERROR] Current binding properties are expanding to more than one permutation but per-file compilation requires that each compile operate on only one permutation.

Combine it with -setProperty values that fix every property, as the code server does. The flag is meant for development; the compiler page lists its other restrictions. -validateOnly -failOnError checks the sources without producing output and is the cheapest first step of a pull-request build.

Runtime performance: lightweight metrics

The collector and its events

The lightweight metrics system is a global JavaScript function, __gwtStatsEvent, that the compiled code and the bootstrap call with an event object. If the host page does not define it, nothing is reported (the compiled code checks for it before building an event). The event is a plain object with these fields:

Field Meaning

moduleName

The GWT module that emitted the event, so several modules on one page can be told apart

subSystem

The component: startup, rpc, runAsync, or your own name

evtGroup

A group of related events that follow a serial order; for RPC it is the request number

millis

Timestamp in milliseconds

type

The step, for example selectingPermutation

Some events carry more: sessionId, method (the RPC proxy and method), fragment and size (fragment downloads) and bytes (RPC payloads). The collector must return true or false to say whether it recorded the event, and it has to be defined before the bootstrap script runs.

A collector for the Bookshelf host page

The Developer Guide’s example writes each event into the page with innerHTML. A collector should never render event fields as HTML (custom events can carry any string), so this one stores text only and prints it with textContent:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Bookshelf (metrics)</title>
    <meta name="gwt:property" content="locale=en">
    <script>
      // Must run before bookshelf.nocache.js
      window.__gwtStatsEvent = function (e) {
        window.__t0 = window.__t0 || e.millis;
        var line = (e.millis - window.__t0) + ' ms | ' + e.moduleName + ' | ' + e.subSystem
            + ' | ' + e.evtGroup + ' | ' + e.type
            + (e.method ? ' | ' + e.method : '')
            + (e.fragment !== undefined ? ' | fragment=' + e.fragment : '');
        (window.__lines = window.__lines || []).push(line);
        return true;
      };
      window.addEventListener('load', function () {
        setTimeout(function () {
          var pre = document.createElement('pre');
          pre.textContent = window.__lines.join('\n');
          document.body.appendChild(pre);
        }, 3000);
      });
    </script>
    <script src="bookshelf/bookshelf.nocache.js"></script>
  </head>
  <body></body>
</html>

Served by a static server and loaded in headless Chrome, the page printed (RPC lines are from the XSRF token request, which got a 404 because no servlet container ran; the events appear all the same):

0 ms | bookshelf | startup | bootstrap | begin
0 ms | bookshelf | startup | bootstrap | selectingPermutation
4 ms | bookshelf | startup | loadExternalRefs | begin
4 ms | bookshelf | startup | loadExternalRefs | end
4 ms | bookshelf | startup | bootstrap | end
4 ms | bookshelf | startup | moduleStartup | moduleRequested
23 ms | bookshelf | startup | moduleStartup | moduleEvalStart
24 ms | bookshelf | startup | moduleStartup | moduleEvalEnd
25 ms | bookshelf | bookshelf | shell | begin
27 ms | bookshelf | bookshelf | shell | end
27 ms | bookshelf | rpc | 0 | begin | XsrfTokenService_Proxy.getNewXsrfToken
28 ms | bookshelf | rpc | 0 | requestSerialized | XsrfTokenService_Proxy.getNewXsrfToken
28 ms | bookshelf | rpc | 0 | requestSent | XsrfTokenService_Proxy.getNewXsrfToken
28 ms | bookshelf | startup | moduleStartup | end
35 ms | bookshelf | rpc | 0 | responseReceived | XsrfTokenService_Proxy.getNewXsrfToken
36 ms | bookshelf | rpc | 0 | responseDeserialized | XsrfTokenService_Proxy.getNewXsrfToken
36 ms | bookshelf | rpc | 0 | end | XsrfTokenService_Proxy.getNewXsrfToken

The startup events come from the bootstrap script, moduleStartup from the module loading (see Linkers & Bootstrap), rpc from the RPC proxies and runAsync from the fragment loader. Opening the admin place added the split point downloads, with the leftover fragment first:

25 ms | bookshelf | runAsync | leftoversDownload | begin | fragment=2
35 ms | bookshelf | runAsync | leftoversDownload | end | fragment=2
35 ms | bookshelf | runAsync | download1 | begin | fragment=1
45 ms | bookshelf | runAsync | download1 | end | fragment=1
45 ms | bookshelf | runAsync | runCallbacks1 | begin
45 ms | bookshelf | runAsync | runCallbacks1 | end

Your own events with JsInterop

The Developer Guide extends the system with a JSNI method. In 2.13 a native JsInterop binding does the same without JSNI, and JsPackage.GLOBAL reached the host page’s __gwtStatsEvent (the compiled code runs inside the xsiframe iframe, and the call still arrived):

@JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
public class StatsEvent {
  public String moduleName;
  public String subSystem;
  public String evtGroup;
  public double millis;
  public String type;
}

public final class Stats {
  private Stats() {
  }

  @JsProperty(namespace = JsPackage.GLOBAL, name = "__gwtStatsEvent")
  private static native Object collector();

  @JsMethod(namespace = JsPackage.GLOBAL, name = "__gwtStatsEvent")
  private static native boolean send(StatsEvent event);

  public static void log(String subSystem, String evtGroup, String type) {
    if (collector() == null) {
      return; // no collector on the host page: cost is one check
    }
    StatsEvent e = new StatsEvent();
    e.moduleName = GWT.getModuleName();
    e.subSystem = subSystem;
    e.evtGroup = evtGroup;
    e.millis = Duration.currentTimeMillis();
    e.type = type;
    send(e);
  }
}

// App.onModuleLoad
Stats.log("bookshelf", "shell", "begin");
ClientFactory factory = new ClientFactory();
// ... build the shell ...
Stats.log("bookshelf", "shell", "end");

The shell begin and shell end lines in the output above come from this code. Several modules on one page report to the same collector with their own moduleName, which allows comparing them (the "multiple modules" section of the guide).

Using the data in production

  • Events contain module names, RPC method names, timings and fragment numbers, not RPC payloads. Do not add user data to custom events.

  • The collector is an inline <script>; with a strict Content-Security-Policy put it in a file or give it a nonce.

  • To send field data to a server, batch the lines and use navigator.sendBeacon from the collector; do not issue a request per event.

  • The Developer Guide mentions a Debug Panel for GWT built on this system; it was not tried here.

JFR events of the compiler (2.13)

The 2.13.0 release notes say: "JFR events added to replace SpeedTracer, support observability into compiler steps, permutation and fragment counts, and output size". The Optimizing page of the Developer Guide still recommends Speed Tracer, which is outdated. These are Java Flight Recorder events in the compiler JVM, not browser data; use the browser’s own developer tools for run-time profiling, with source maps from Super Dev Mode & Debugging. The 2.13.1 release added the GWT version and commit to the output, so recordings can be compared across releases.

Recording

The events are ordinary jdk.jfr.Event subclasses in the GWT category with @StackTrace(false). They are written when a recording is running in the compiler JVM. With Maven the compiler is a forked JVM, so pass the option in jvmArgs:

<configuration>
  <jvmArgs>
    <arg>-XX:StartFlightRecording:filename=${project.build.directory}/gwt-compile.jfr,settings=default</arg>
  </jvmArgs>
</configuration>
mvn -B package -pl bookshelf-client -am -DskipTests
$JAVA_HOME/bin/jfr summary bookshelf-client/target/gwt-compile.jfr
$JAVA_HOME/bin/jfr print --events gwt.compiler.js.Fragment bookshelf-client/target/gwt-compile.jfr

The summary of the six-permutation compile listed these GWT events (count of events in the recording):

Event Count What it records

gwt.Startup

1

GWT version (GWT Web Toolkit 2.13.1), git revision and the main class (com.google.gwt.dev.Compiler)

gwt.SimpleEvent

3,798

A named task and its duration, for example Precompile, Compile Permutations, Link, Unify AST

gwt.compiler.CompilerPass

739

One optimisation or normalisation pass

gwt.compiler.OptimizationLoop

72

An iteration of the Java or JavaScript optimisation loop, with numMods and nodeCount

gwt.compiler.Permutation

6

Duration and property values of each permutation

gwt.compiler.js.Fragment

18

Permutation id, fragment index and jsBytes

gwt.compiler.java.Count, gwt.compiler.js.Count

60 and 24

AST node count

gwt.compiler.Rebind, gwt.compiler.GwtCreate

438 and 95

Deferred-binding resolutions (see Deferred Binding & Generators)

gwt.ext.Generator

192

Time per generator class

gwt.compiler.java.TypeOracleUpdate, gwt.compiler.java.AddGeneratedCompilationUnits, gwt.compiler.ModuleDefEvent

155, 30 and 4

Module loading and type oracle phases

The fragment events tie the two tools together. For permutation 0 the recording has fragment 0 with jsBytes = 259606 and fragment 1 with jsBytes = 658; the compile report of the same permutation reported an initial download of 259,606 bytes and a split point of 658.

Reading a recording

Printing as JSON and aggregating gave this picture for the Bookshelf compile:

Task or pass Time

Precompile

8.1 s

Compile Permutations

7.2 s

CSB build from oracle

6.1 s

JdtCompiler Source Oracle

5.6 s

Unify AST

1.5 s

TypeTightener (all permutations)

1.2 s

Pruner

0.9 s

MakeCallsStatic

0.8 s

generator InlineClientBundleGenerator

0.36 s

generator UiBinderGenerator

0.08 s

Nested tasks overlap, so the figures do not add up. Two conclusions followed: the time before the permutations (parsing and type resolution) is as large as the optimisation, and generators are almost free here, so trimming UiBinder templates would not shorten this build while fewer permutations would.

The gwt.jjs.dumpAst system property, which the release notes say was tweaked in 2.13.0, dumps the Java and JavaScript AST between passes to a file; gwt.jjs.dumpAst.filter restricts the output to source files and line ranges. It is a compiler-developer tool and is only mentioned for completeness.

Worked example: a Bookshelf size and time budget

Step Action Result

1

Compile with -compileReport

Initial download 262,286 bytes; cellview is 15.6 %; the admin split point is 633 bytes

2

Open the dependency chain of the grid

DataGrid::DataGrid pulls the grid resources into the initial fragment

3

Replace the BigDecimal fee by int cents

-24,618 bytes (-9.3 %) in the experiment

4

Release profile: -XnoclassMetadata after removing class-name logging, jre.checks.checkLevel=OPTIMIZED

-7.5 % and -1.3 % of the total bytes

5

Pull-request profile: one permutation, -draftCompile

about 5 to 8 seconds instead of 11 to 17

6

Serve .cache.js compressed

265,852 to about 89,500 bytes on the wire

7

Add the metrics collector to a staging host page

startup and RPC timings per visitor, no code change in the application

Pitfalls

Symptom or message Cause Fix

Current binding properties are expanding to more than one permutation but per-file compilation requires that each compile operate on only one permutation.

-incremental with several permutations

Fix every property with -setProperty, or drop -incremental

No compileReport parameter in the Maven plugin

Not a gwt:compile parameter

Use compilerArgs

"Compiler Metrics" link in the report opens nothing

-XcompilerMetrics was not set

Add -XcompilerMetrics

Class names such as Class$17 in logs

-XnoclassMetadata

Do not rely on getName() in that build

No bootstrap or moduleRequested events

__gwtStatsEvent was defined after the nocache.js script tag (only moduleEvalStart, moduleEvalEnd and later events arrived)

Define it before the bootstrap script

JFR file lacks most permutations

With -localWorkers 3 the recording had 2 of the 6 gwt.compiler.Permutation events, because other workers are separate JVMs

Profile with the default single worker

Report numbers look like 262.930

Number format of the compiler JVM’s default locale

Read it as 262,930, or set a locale for the compile

Compile times vary by 50 % between runs

Other processes on the machine

Compare bytes, or repeat and take the minimum

A draft compile is 600 KB per permutation

-draftCompile and -optimize 0

Never deploy them

Report published with the site

-extra pointed into the web root

Keep -extra outside the war

Recent changes and outdated material

  • 2.13.0. JFR events replace Speed Tracer for compiler observability; 2.13.1 adds the GWT version and commit to them.

  • Optimizing page. The Developer Guide page still points to Speed Tracer and uses innerHTML in its metrics sample; both are outdated.

  • JSNI. The extension example of the lightweight metrics page uses JSNI; JsInterop does it without JSNI.

  • The book. The first edition of GWT in Action predates GWT 2 and does not cover compile reports, lightweight metrics or JFR.