Rise of J2Cl: Java web development after GWT

It looks like 15 years of GWT are coming to the end, and besides that web development has dramatically changed since 2006. There is now no chaos of conflicting browser implementations that require to run multiple permutations each. At the same time modern web development frameworks are far from ideal. For instance, a very strong advantage of GWT was the ecosystem around Maven – the stability and usability of this solution was incredible, especially when big teams worked on large projects.

Google, the main developer of GWT, left the project and started J2CL, the successor of GWT, which takes the very best practices to a new level. Their documentation calls it out as being used in many high performance projects such as Gmail, Inbox, Docs, Slides, and Calendar.

Initially J2CL was developed to be used in the Bazel environment. After several years of hard work the community, led by Colin Alworth, released the first public J2CL version for Maven – j2cl-maven-plugin.

So let’s take a look at what it is and how it works.

J2CL and Closure Compiler

J2CL is responsible for only one task – to transpile a set of Java classes into a set of JavaScript files.

Google’s Closure Compiler is responsible for merging this set of javascripts into one executable JS script, its optimization and minification.

Closure Compiler is extremely efficient in minification and optimization of the JavaScript, it simply has no competitors.

Generating our first J2CL project

Let’s start from a simple one module application. Luckily for us, we can generate it from a pre-build archetype. Download the archetype if you don’t have it: 

mvn org.apache.maven.plugins:maven-dependency-plugin:get \
-DrepoUrl=https://repo.vertispan.com/j2cl/ \

Now we can generate a simple application:

mvn archetype:generate -DarchetypeGroupId=com.vertispan.j2cl.archetypes \
-DarchetypeArtifactId=j2cl-archetype-simple \

Let’s take a look at the result:

├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── treblereel
    │   │           └── j2cl
    │   │               ├── App.java
    │   │               └── App.native.js
    │   └── webapp
    │       ├── WEB-INF
    │       │   └── web.xml
    │       ├── css
    │       │   └── simpleapp.css
    │       └── index.html
    └── test
        └── java
            └── org
                └── treblereel
                    └── j2cl
                        └── AppTest.java

– App.java is a starting point of our application and there’s is one point I have to highlight below.

– App.native.js used to specify how to start our application to the Closure Compiler, because it knows nothing about it. Usage of native.js is a very large topic and a separate article can be written about it.

– AppTest.java is just a J2CL-compatible unit test that runs in HtmlUnit, it’s also possible to use ChromeDriver to run it in a real browser but it takes longer.

– pom.xml – here the only interesting part for us is the j2cl-maven-plugin section. For now it contains only the <compilationLevel> declaration used to set which level of optimization we are going to use during the compilation of the project. ADVANCED is the most efficient one, so Closure Compiler does aggressive renaming, dead code removal, global inlining and so on. But in some cases Closure Compiler needs our help and care – we have to declare which properties or methods should not be removed or renamed. BUNDLE is less strict and better suitable for development because each compilation round takes less time compared to ADVANCED. 

Running and building our J2CL application

j2cl-maven-plugin allows us to run our application in the development mode with build-in hot code reload and source map debug. To start devmode, run the following command in the terminal:

> mvn j2cl:watch

When the application started, run the following command in the second terminal:

> mvn jetty:run

There is no need to run ‘mvn clean’ each time because J2CL will recompile everything from scratch, and we can reuse the results from the previous run. Moreover, there is an option to use global cache between several projects to reduce compilation time.

To build ‘.war’ we should run ‘mvn package’, there is nothing new here, everything is pretty familiar to GWT developers.

Ok, what is new compared to GWT

  • GWT modules are gone, yes, no more modules. So J2CL will try to compile direct and transitive dependencies from pom.xml, that is why we should set ‘provided scope’ to annotation processors and shade them.
  • GWT.create gone as well
  • GWT generators are gone, now we should use APT-based generators.
  • What about GWT components and widgets we used before ? Most of them have been ported to J2CL.
  • Does @GwtIncompatible work? Yes, it is still here.

And what is the value of it for us?

Right now we are focused on migration of our existing projects from gwt2 to j2cl. There are many libraries that have been migrated to j2cl and there are many libraries that support gwt2 and j2cl. I would like to highlight elemental2-* wrappers, DominoKit, Nalu, and many others.
Gwt2 has been ported as gwtproject – set of migrated modules. Errai framework – the core component of our applications has been re-implemented as Crysknife project.

In the upcoming posts i am going to address several topics:

  • Existing libraries and frameworks that are J2CL-compatible
  • Defines – how to propagate variables to J2CL
  • How we can improve generated code with native.js
  • Externs – why do we need them and how to write our own externs
  • Semi-reflection 
  • Interoperability with TS
  • And maybe many more


4.8 12 votes
Article Rating
Notify of
Newest Most Voted
Inline Feedbacks
View all comments