Renato Athaydes Personal Website Sharing knowledge for a better world Home Posts About

Comparing JVM alternatives to JavaScript

I am mostly a backend developer, but every now and then I need to do some frontend work. Even if just for personal use.

For that reason, I’ve been following developments in the JavaScript world from a distance, but I dislike much of what I see.

The thing I dislike most is how complex the toolchain for a professional JavaScript application has become. The days when you wrote a few lines of HTML, opened it on the browser with file:///home/me/index.htlm , then linked to a simple JS file to add dynamic behaviour - with updates only a page refresh away - seem to be long gone.

The MDN page on JavaScript actually shows how to use HTML and JS as it was intended - no build tools, no frameworks. If only people listened.

The JavaScript toolchain of today has all the complexity of backend toolchains, and then some. And just a handful of years ago, many parts of the toolchain were different! Checking some of the answers on this StackOverflow question is hilarious.

This Pluralsight JS development environment course suggests you need 32 tools to build your application (e.g. babel, chai, cheerio, eslint, mocha, webpack…).

This article from 2016 that keeps coming up on Google and DuckDuckGo searches shows how a basic JS development environment consists of:

dependency management tool (npm or yarn).

module bundling (webpack or gulp, browserify, bower).

ES6 compilation (babel).

task automation (npm scripts).

live reload (live-server).

If you follow instructions from that article, you end up with 3 config files (one for each of npm, babel and webpack), your build steps are managed by shell scripts embedded into a JSON config file, and your project now requires a compilation step and a live reloading HTTP server to run.

At this point, I thought that if I am going to be compiling stuff, I should at least use a compiler that can type check my code!

So why not try TypeScript?!

I then followed this tutorial to start a TypeScript project.

Now, on top of those config files I mentioned earlier, we get 2 more: tsconfig.json and tslint.json .

It’s all so complex Google has even created a tool (GTS, or Google TypeScript Style) to manage TypeScript config.

I tried to use that but then the browser refused to run my JavaScript file with some module-related error… I don’t know which of the myriad tools I was using could fix this for me, so I just decided to look for alternatives to this madness!

In doing so, I found this really nice blog post proclaiming that You might not need a build toolchain.

It shows that it’s still possible to use modern tools like React without build tools!! But unfortunately, that is not very practical once you start using dependencies and your application grows larger than a few hundred lines of JavaScript.

Back to square one.

Given that a build/compilation step seems to be a necessary evil no matter what we do, and that we’ve known how to write large applications for a long time in the backend using decent tools, why not try to use those in the front end too!?

After all, back in 2011 when I was doing web development, people were already experimenting with that idea… for example, I was using GWT, which is a Java-based web toolkit, in 2011… surely, 8 years later, the scene should have improved a lot for Java-based web applications?

I set out to see what’s available out there. Can a backend developer who is happy to stay with the nice tooling and great ecosystem of the Java world do frontend work without jumping ship to the madness of the JS world?

Get ready to find out!

Methodology

As a means of comparing each option listed in this blog post, I decided to create a very simple counter app, just like the React app demonstrated in the

You might not need a build toolchain blog post I mentioned earlier.

For reference, here’s the code for that app using React (without JSX):

The methodology I used to compare the different options is very straightforward:

find a getting-started guide and run the demo application to see how easy it is set it up. check how well the framework fits into what Java developers expect. create the counter app using the most basic tools provided by the product/framework. measure the application’s size, LOC and performance.

The last bullet point was inspired by the blog post A real-world comparison of front-end frameworks.

The application size is determined by looking at the browser’s network tab (all types of resources are included to avoid favouring frameworks that rely on heavy, non-JS resources).

The performance, using the auditing tool from Google Chrome’s built-in Lighthouse.

Here are the React’s results for reference.

The React JS files were downloaded and served from the local server instead of from a CDN to make comparisons fair.

Application size:

Performance results:

Now, we know what we’re up against!

So, here we go, starting with the grandpa of all Java-based web toolkits: GWT.

Click on the titles below to expand each section.

GWT - Java-source-to-JS, server and client-side framework GWT Website: http://www.gwtproject.org Probably one of the first efforts to get Java in the browser without Applets or plugins, GWT has been around for a long time (since 2006). It’s a very mature technology now, but it has not been a Google-backed framework since 2013… I remember at the time Google seemed to be focusing its web efforts on Dart, which competed directly with GWT in the space of JS alternatives, and the GWT community was feeling abandoned. Anyhow, the project seems to be still around, so I thought I must include it in this comparison to really understand how the more modern alternatives are doing. To get started with GWT, their website recommends either downloading the SDK or installing the Eclipse Plugin… I haven’t used Eclipse in years (I’ve migrated to IntelliJ and never looked back) so I took the first option: ./webAppCreator -out gwt-app com.athaydes.GwtApp This command runs very quickly and creates an Ant project (what?!). I have been working with Java for over 10 years and even I missed out on Ant heydays - Maven was already the Java build tool of choice if I remember correctly. But I don’t mind Ant! Converting it to a Gradle or Maven build later should be almost trivial. Not to mention that IntelliJ has great support for Ant projects, so you can open the project in IntelliJ and everything will work. To run the demo app: ant devmode This opens up an unmistakable Swing app which lets you control the development server! I could swear it looks the same as it did in 2009! Opening the actual app on the browser, I can see that, apart from some fresher styles, the app also looks pretty much the same. The demo app tries to show how easy it is to use GWT’s strongest feature: the seamless RPC-framework which lets developers almost forget about the distinction between client and server, letting the two communicate as easily as calling Java methods. But for the purpose of building the counter app, we don’t need that. So the first thing I did was to remove the RPC stuff from the getting-started app. A GWT application normally consists of a single sources root which is divided into: client - client-side only code that runs in the browser.

server - server-side only code - the backend.

shared - code visible from both client and server. you can configure which packages are visible on the client-side part of the app in the GwtApp.gwt.xml file, at the top-level package of the app. This file also controls the GWT Theme the app should use, so you can, for example, easily make your app use a dark theme. In this get-started app, the Java sources are in src/ and the web stuff in war/ . The entry point HTML file can be found at war/GwtApp.index.html . Looking at it, it consists of a fairly normal HTML file, but strangely, most of the view is already there - implemented using a <table> layout (clearly, this was created circa 2008). I wanted to implement the view using code, so I removed the table thing and added a <div id="content"></div> . Implementing UIs in GWT is very easy because of the widget-based API. So, I was able to create the counter app in less than 5 minutes (please keep in mind I have prior experience with GWT, even though I barely remembered anything). But when I tried to re-compile the code, it complained about my lambdas - the ant file declares we want to use Java 7! Someone should update that!! GWT does support Java 8, so I updated that to Java 8 and all was good. This is the code I came up with to implement the counter app in GWT: package com . athaydes . client ; import com . google . gwt . core . client . EntryPoint ; import com . google . gwt . user . client . ui . * ; import java . util . function . Consumer ; public class GwtApp implements EntryPoint { public void onModuleLoad() { RootPanel. get ( "content" ). add ( new Counter() ); } } class Counter extends Composite { private int value; Counter() { HorizontalPanel buttonsPanel = new HorizontalPanel(); buttonsPanel. setSpacing ( 10 ); Button up = new Button( "Increment" ); Button down = new Button( "Decrement" ); Label out = new Label(); Runnable update = () - > out. setText ( "The current count is " + value ); update. run (); Consumer < Boolean > handler = ( increment ) - > { if ( increment ) { value + + ; } else { value - - ; } update. run (); }; up. addClickHandler ( clickEvent - > handler. accept ( true ) ); down. addClickHandler ( clickEvent - > handler. accept ( false ) ); buttonsPanel. add ( up ); buttonsPanel. add ( down ); VerticalPanel root = new VerticalPanel(); root. setSpacing ( 20 ); root. add ( buttonsPanel ); root. add ( out ); initWidget( root ); } } Quite nice. Hot reloading works with the Super Dev Mode so fixing little things in the UI is quick and easy. To compile the production version of the app: ant build The generated JS files are placed in the war/gwtapp directory. Serving the war directory is enough to run the app in a browser, so I just open war/GwtApp.html in IntelliJ and click on the little browser icon on the top-right to let IntelliJ start a HTTP server serving the HTML file and open that in whatever browser I want. Worked perfectly. Application size: Performance results:

TeaVM - Java-bytecode-to-JS compiler TeaVM Website: http://teavm.org/ TeaVM is a project that claims to be able to emit JavaScript and Webassembly from Java bytecode. Because it doesn’t require Java sources, it can also be used to compile from Kotlin and Scala (and probably other JVM-based languages). To get started with TeaVM, use the Maven archetype: $ mvn -DarchetypeCatalog=local \ -DarchetypeGroupId=org.teavm -DarchetypeArtifactId=teavm-maven-webapp -DarchetypeVersion=0.5.1 archetype:generate This will create a fairly standard Java project within a directory with the name you gave when prompted, following Maven conventions. Java sources are in src/main/java , web resources like index.html in src/main/webapp . Here’s the Java code I wrote for our little counter: package com . athaydes ; import org . teavm . jso . dom . html . HTMLButtonElement ; import org . teavm . jso . dom . html . HTMLDocument ; import org . teavm . jso . dom . html . HTMLElement ; import java . util . function . Consumer ; public class Client { public static void main ( String[] args ) { HTMLDocument document = HTMLDocument. current (); HTMLElement div = document. createElement ( "h2" ); div. appendChild ( document. createTextNode ( "TeaVM CounterApp" ) ); document. getBody (). appendChild ( div ); div. appendChild ( new Counter(). build () ); } } class Counter { private int value = 0; HTMLElement build () { HTMLDocument document = HTMLDocument. current (); HTMLButtonElement up = ( HTMLButtonElement ) document. createElement ( "button" ); up. appendChild ( document. createTextNode ( "Increment" ) ); HTMLButtonElement down = ( HTMLButtonElement ) document. createElement ( "button" ); down. appendChild ( document. createTextNode ( "Decrement" ) ); HTMLElement out = document. createElement ( "p" ); Runnable update = () - > out. setInnerHTML ( "The current count is " + value ); Consumer < Boolean > handler = ( increment ) - > { if ( increment ) { value + + ; } else { value - - ; } update. run (); }; up. listenClick ( ( event ) - > handler. accept ( true ) ); down. listenClick ( ( event ) - > handler. accept ( false ) ); update. run (); HTMLElement div = document. createElement ( "div" ); div. appendChild ( out ); div. appendChild ( up ); div. appendChild ( down ); return div; } } Interestingly, the Java Counter component is 34-lines long, quite a bit shorter than the equivalent React.js equivalent, which has no less than 55 lines! To compile, run: $ mvn package This will compile all Java code as usual, producing .class files in the target/classes directory (like a normal Java project), but it will also create the JavaScript output under target/<project-name>-<version>/teavm , or in my case, as I named the project mytea , target/mytea-1-0-SNAPSHOT/teavm . To run the application, simply start a web server to serve the ` target/mytea-1-0-SNAPSHOT/ directory. Application size: Performance results:

JSweet - Java-source-to-JS (and TypeScript) compiler with library ecosystem JSweet Website: http://www.jsweet.org/ JSweet compiles Java source code to both TypeScript and JavaScript. Java libraries can be published as TypeScript libraries (or candy, as they call it). Unlike the other options, the Java code can fully interop with the JS/TS code in both directions! With JSweet, the recommended way to get started is by cloning the quick-start project from GitHub: $ git clone https://github.com/cincheo/jsweet-quickstart.git $ cd jsweet-quickstart $ mvn generate-sources Again, the Java project uses the standard Maven convention with Java sources in src/main/java . However, web resources are placed directly under the webapp directory. Unlike with TeaVM, the JSweet compiler does not bother generating class files at all: that’s why you can run just mvn generate-sources instead of the more usual mvn package or mvn install , to generate the JS code. The Java code I ended up writing is almost identical to the TeaVM one _(with one notable exception: the JSweet API chose java.util.function.Function for click handlers instead of the expected Consumer<Event> , so they must return a value, making the lambda pretty awkward to write, with curly braces and a mandatory return null at the end): package quickstart ; import def . dom . HTMLButtonElement ; import def . dom . HTMLElement ; import java . util . function . Consumer ; import static def. dom . Globals . document ; public class QuickStart { public static void main ( String[] args ) { HTMLElement div = document. createElement ( "h2" ); div. appendChild ( document. createTextNode ( "JSweet CounterApp" ) ); document. body . appendChild ( div ); div. appendChild ( new Counter(). build () ); } } class Counter { private int value = 0; HTMLElement build () { HTMLButtonElement up = ( HTMLButtonElement ) document. createElement ( "button" ); up. appendChild ( document. createTextNode ( "Increment" ) ); HTMLButtonElement down = ( HTMLButtonElement ) document. createElement ( "button" ); down. appendChild ( document. createTextNode ( "Decrement" ) ); HTMLElement out = document. createElement ( "p" ); Runnable update = () - > out. innerText = "The current count is " + value; Consumer < Boolean > handler = ( increment ) - > { if ( increment ) { value + + ; } else { value - - ; } update. run (); }; up. onclick = ( event ) - > { handler. accept ( true ); return null ; }; down. onclick = ( event ) - > { handler. accept ( false ); return null ; }; update. run (); HTMLElement div = document. createElement ( "div" ); div. appendChild ( out ); div. appendChild ( up ); div. appendChild ( down ); return div; } } Application size: Performance results:

CheerpJ - Full JVM implementation on the browser CheerpJ Website: https://leaningtech.com/cheerpj/ CheerpJ is definitely the craziest option we’re going to look at (it may even be the craziest project you’ll see, period!). It is not really a Java-to-JavaScript transpiler… it’s an actual complete Java runtime! That’s right. It can do everything a more conventional JVM can do. Threads, file system (apparently, it emulates a file system via IndexDB), reflection, class loading, networking… you name it. If that’s not enough to make your head spin, then let me introduce you to the CheerpJ’s Get Started Tutorial. It shows how you can take a compiled jar containing a Swing app (remember Swing, that multi-platform Java UI framework for the desktop??) and run it right on the browser… without plugins. It’s not an applet runner. It actually runs the Swing code!! In the browser!!! Without plugins!!!! I didn’t really believe it, so I followed along with the tutorial to check this out for myself… I’ll summarize the steps here for your convenience: Download a Swing app’s jar.

Run it using your local Java just to see what it looks like.

Install CheerpJ (you do have to manually download it and put it under your home folder, they say).

Make sure you have python3 installed (I know, right? Python is going to be part of your build if you want to use CheerpJ).

installed (I know, right? Python is going to be part of your build if you want to use CheerpJ). Run ~/cheerpj_1.3/cheerpjfy.py TextDemo.jar .

. Create an index.html file. Gist.

file. Gist. Serve the current directory with a real web server. Now, it didn’t look like it was going to work as the loader just hanged there for a while… but after a longer-than-what-you-would-probably-be-willing-to-wait wait, there it was! The same Swing app I had just run with local Java, but embedded within a web page inside the browser! Swing TextDemo running on Ubuntu Swing TextDemo running on the browser via CheerpJ If you have an old Swing app laying around, CheerpJ may be for you… but if you’re looking to write a new application, you probably want to stay well away… I tried to implement the Counter App using CheerpJ’s support for the DOM (a cheerpj-dom.jar file is included in the distribution)… but I ran into 2 bugs in like 10 minutes. And they were bad enough to block me completely… not to mention that I had to wrap every single Java String into a call to Global.JSString() ! Hint to the CheerpJ developers: put your jars in a Maven repo - no self-respected Java developer is going to hardcode a dependency to a local file - and no, putting it under the home directory is not ok! Also, learn the language conventions: don’t call a Java method set_onclick … it’s setOnClick thank you. I was ready to give up on CheerpJ, but then I remembered one of my methodology statements: create the counter app using the most basic tools provided by the product/framework. Well, in the case of CheerpJ, it seems the basic tool provided for UIs is the JVM itself! And the JVM offers a UI kit: Swing! As of Java 12, Swing is the only UI Kit distributed with the JVM… JavaFX has become a separated project. And in any case, CheerpJ only supports Swing. So here’s my implementation of the Counter App in pure Swing: import javax . swing . * ; import java . awt . * ; import java . util . function . Consumer ; public class Main implements Runnable { @Override public void run() { JFrame frame = new JFrame( "CheerpJ Demo" ); frame. setDefaultCloseOperation ( JFrame. EXIT_ON_CLOSE ); frame. setSize ( 400, 80 ); frame. add ( new Counter() ); frame. setVisible ( true ); } public static void main ( String[] args ) { SwingUtilities. invokeLater ( new Main() ); } } class Counter extends JPanel { private int value; public Counter () { JButton up = new JButton( "Increment" ); JButton down = new JButton( "Decrement" ); JLabel out = new JLabel(); Runnable update = () - > out. setText ( "The current count is " + value ); update. run (); Consumer < Boolean > handler = ( increment ) - > { if ( increment ) { value + + ; } else { value - - ; } update. run (); }; up. addActionListener ( event - > handler. accept ( true ) ); down. addActionListener ( event - > handler. accept ( false ) ); JPanel buttonsPanel = new JPanel(); buttonsPanel. add ( up ); buttonsPanel. add ( down ); JLabel title = new JLabel( "CheerpJ CounterApp (Swing)" ); setLayout( new BorderLayout() ); add( title, BorderLayout. PAGE_START ); add( buttonsPanel, BorderLayout. PAGE_END ); add( out, BorderLayout. CENTER ); } } This will run fine on the desktop: it’s written as a completely normal Swing app! We just need to create the totally normal jar first: mvn package Run with Java: java -jar cheerpj-swing/target/cheerpj-swing-1.0-SNAPSHOT.jar And here it is: CounterApp running on Ubuntu The crazy thing is that it can also run in the browser with CheerpJ. Looking at the Network tab on the browser was fun: it kept downloading stuff, apparently there’s one JS file for each Java’s standard library package, and CheerpJ downloads them on demand. Application size: Performance results: To understand why it looks so bad, here’s the helpful diagnostics Lighthouse gives: This is the first time I see the Avoid enourmous network payloads diagnostic, and I am definitely going to try to follow that advice!

Vaadin Flow - Java-source-to-JS-source, server and client-side framework Vaadin Flow Website: https://vaadin.com/flow I had heard of Vaadin a long time ago as a GWT-based framework… but it seems that they recently created a simpler product based on web components called Vaadin Flow. I like web components, so I decided to check that out! The starter page has several options to start a project integrating with things like Spring or CDI. I chose the most basic option I could find, Project Base, to start simple as all I want is a client-side only application. I downloaded the zip, as instructed, and got the code up and running in a couple of minutes. Once again, it was a familiar Maven project (I expected Gradle to be the most popular choice these days, but apparently not)… there was no index.html file in sight, which I found surprising. How exactly does the browser load the app? Anyway, to run the app we just need to run a magic command: mvn jetty:run , then go to http://localhost:8080 , as one would expect, to visit the running app. Looking at the huge POM file (139 lines for a hello world app), it seems they use the jetty-maven-plugin for that. So, again, I can’t use my favourite option, the IntelliJ server, to simply serve the index.html file and start messing around. This is a little bit too much magic to me… sure, I’m a Java developer, but come’on. I can handle a HTML file or two. While I was changing stuff, it looked like the server was restarting, which made me think it was doing hot reloading. Great, I thought! But unfortunately, whatever the server was doing, it was not hot reloading my code. I tried refreshing the page a few times but that only caused errors!! Looks like Vaadin apps don’t like page refreshes (it was in debug mode - but still, it’s probably doing too much magic for its own good, and is now crumbling under its own complexity). The Java API for writing UIs, however, is excellent. Easy to use and learn, it was obviously written for Java programmers to feel at home, which is great! Just look at the code I wrote for the Counter app, and see if you don’t agree it’s pretty awesome: package com . example . test ; import com . vaadin . flow . component . Composite ; import com . vaadin . flow . component . button . Button ; import com . vaadin . flow . component . html . Div ; import com . vaadin . flow . component . html . H2 ; import com . vaadin . flow . component . html . Paragraph ; import com . vaadin . flow . component . orderedlayout . VerticalLayout ; import com . vaadin . flow . router . Route ; import java . util . function . Consumer ; @Route( "" ) public class MainView extends VerticalLayout { public MainView () { H2 header = new H2( "Vaadin Flow CounterApp" ); add( header, new Counter() ); } } class Counter extends Composite < Div > { private int value; Counter() { Paragraph out = new Paragraph(); Runnable update = () - > out. setText ( "The current count is " + value ); update. run (); Consumer < Boolean > handler = ( increment ) - > { if ( increment ) { value + + ; } else { value - - ; } update. run (); }; Button up = new Button( "Increment" , event - > handler. accept ( true ) ); Button down = new Button( "Decrement" , event - > handler. accept ( false ) ); getContent(). add ( out, up, down ); } } To measure the performance of the app, I ran it in production mode, which is done with this command: $ mvn jetty:run-exploded -Pproduction-mode The page feels quite snappy and, without any effort from me, styled nicely already. Application size: Notice how my little Vaadin counter needed nothing less than 312KB to run (mostly in a huge HTML file, vaadin-flow-bundle - the total size of the JS resources was only 46.7KB)… more than I would’ve hoped. Performance results: Chrome showed a warning on the console while running the app, which is a little bit concerning. It seems Vaadin has been an early adopter of experimental features such as HTML imports, which are now being abandoned. Too bad, but as long as they keep their product up-to-date, this shouldn’t be a big issue. Vaadin is a fair bit slower than JSweet and TeaVM, but to be fair, this version of the counter is doing a little bit more than the others, though not voluntarily (is there a way to remove styling and fonts from a Vaadin app??)!

Bck2Brwsr - Java-bytecode-to-JS compiler Bck2Brwsr Website: http://wiki.apidesign.org/wiki/Bck2Brwsr Bck2Brwsr started in 2012 with the goal of creating a small Java capable to boot fast and run in 100% of modern browsers. Unlike CheerpJ, however, it does not promise a full JVM implementation, only a small sub-set that makes sense on the browser, more similar to TeaVM as it also compiles from Java bytecode to JS. There is an old looking wiki at apidesign.org which shows how to get started with bck2brwsr . The first suggestion on the page was called Bck2BrwsrViaCLI, the second, Knockout4Java. I didnt’ really want to use Knockout.js - it went out of favour in the JS world since around 2015 - so I went with the CLI option. It instructs us to run the Maven archetype to create a new project like thus: $ mvn archetype:generate -DarchetypeGroupId=com.dukescript.archetype -DarchetypeArtifactId=knockout4j-archetype -DarchetypeVersion=0.26 -Dwebpath=client-web Careful with the command shown on their wiki! It’s missing a back-slash on the second last line, and it points to version 0.16 , when the current version as of writing is 0.26 . Then, compile and package the app with: $ mvn package Warning: this will open up a popup window with a JUnit Browser Runner . Didn’t expect that… moving on, it says we can run the app with something called FXBrwsr by running: $ mvn -f client process-classes exec:exec This didn’t work for me. I got this error: Exception in thread "main" java.lang.IllegalStateException: Can't find any Fn.Presenter at net.java.html.boot.BrowserBuilder.showAndWait(BrowserBuilder.java:255) at com.athaydes.Main.main(Main.java:16) [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ Quickly googling around turned up nothing (my guess is that they haven’t tested this on Linux), so I tried the next suggestion to run the app in a real browser: $ mvn -f client-web clean package -DskipTests bck2brwsr:show This gave another error: [ERROR] Failed to execute goal on project bck2brwsr-web: Could not resolve dependencies for project com.athaydes:bck2brwsr-web:jar:1.0-SNAPSHOT: Could not find artifact com.athaydes:bck2brwsr-js:jar:bck2brwsr:1.0-SNAPSHOT -> [Help 1] This is a classic problem with Maven: you need to install your artifacts sometimes to make them visible to certain plugins… to fix that you normally run: $ mvn install Running the command to start the app again after this seemed to work. But the app didn’t do anything. Checking the console, sure enough, there was an error: VM69 bck2brwsr.js:6 Uncaught Cannot find com.athaydes.BrowserMain w @ VM69 bck2brwsr.js:6 v @ VM69 bck2brwsr.js:2 r @ VM69 bck2brwsr.js:2 load (async) z @ VM69 bck2brwsr.js:3 r @ VM69 bck2brwsr.js:4 w @ VM69 bck2brwsr.js:6 n.loadClass @ VM69 bck2brwsr.js:6 (anonymous) @ index.html:28 I looked around… tried to find something that could help me understand the problem. But I couldn’t… What I could find, however, was a huge amount of XML. 931 of lines, to be precise, distributed across 4 Maven poms. I also found the project consists of 3 Maven modules: client , client-web and js . The js module contains a Java class with a few methods marked native and annotated with @JavaScriptBody annotations containing the JavaScript code implementing the methods, embedded into Java Strings (ugh!). The client module contained a bootstrapping main class as well as a DataModel class with even more annotations, seemingly trying to bind the data model with the view, while trying to use the functions declared in the js module. It referred to another class called Data that doesn’t exist in sources… it is auto-generated when the project is compiled. I couldn’t for the life of me figure out what is generating that class. It’s probably buried somewhere in the nearly 1000 lines of pom. The client-web module contained the missing class: com.athaydes.BrowserMain . Because that’s the module we run in the Maven command above, I have no idea why the class wouldn’t be found by the browser. To create the UI, I was hoping to be able to use a DOM API, as in some of the previous examples. I searched everywhere for a way to do that, but all I found was some NetBeans APIs, and they were absolutely not what I was looking for (check for yourself). It seems the only way available to interact with the web page is via data-bind attributes on the actual HTML text, powered by, wait for it, Knockout.js! I thought I had avoided the Knockout option, but look at the ID of the Maven archetype they showed on the “CLI” page! Maybe it’s also possible to do that by using the horrendous @JavaScriptBody annotation to write JavaScript code directly from within Java Strings (please don’t do this… for your own sanity’s sake), but I didn’t want to find out. I decided that enough is enough. There’s nearly no docs to help me debug issues like this, and searching for answers online is a waste of time - there are no answers. I am sorry to leave no actual performance data about bck2brwsr in this blog post, but I’ve spent the best part of a day trying to do that and failed… I think it’s fair to say that, unless you have an extremely compelling reason to do so, you should make sure to never touch anything related to this project, including DukeScript. You may notice several references to DukeScript throughout the bck2brwsr documentation and even source code. The example shown in the DukeScript’s Get Started Page is exactly the same as the one created by the Maven archetype I used, so the two projects seem to be closely related - though the Dukescript founders say bck2brwsr is only a small part of the project, and the most experimental one, and they also support TeaVM.

Conclusion

Let’s jump straight to a summary of the results:

bck2brwsr is not included because I couldn’t get it working. If anyone points out what went wrong with my attempts, I will update the results.

Performance

* FCP = First Contentful Paint (this value was used to break the tie between tools with the same performance score)

Size

* CheerpJ’s result was too large to be shown on scale, so it is shown here 10 times smaller than it is

Lines of Code

Lines of Code count excludes imports and comments.

Summary

Q. Performance is of the essence?

A. You should probably pick JSweet or TeaVM.

Q. Do you like a small footprint?

A. JSweet wins by a large margin, but TeaVM is also pretty good.

Q. Do you want to have the smallest code base to maintain?

A. Vaadin Flow is great for that. The other options are all very close to each other, so it probably wouldn’t make much of a difference… they all still manage to beat React!

Q. Do you want something as close as possible to normal Java?

A. Vaadin Flow if you care mostly about UI components, TeaVM if you care about business logic.

Q. Do you want something as close as possible to the JS ecosystem, but still using Java tools?

A. JSweet is perfect for you.

Q. Do you want something that is Java, but in the browser!

A. Ha! CheerpJ actually manages to do that! It has a heavy cost, but it works as advertised.

Q. Want to try something different from Java, just not JS?

A. Elm is an accessible, functional programming language designed to create web applications. It has great tooling, including an awesome IntelliJ Plugin, so it’s perfect for Java developers who always wanted to go functional! Another option a little closer to home is definitely Dart. Very similar to Java but with lots of syntactic sugar to make common tasks easy. Ant it comes with great tooling, including a hot-reload server, and frameworks like Angular and React if you need them.

If you’re curious how the JVM alternatives compare with Dart, I added the Dart implementation to the GitHub repo. The results: Application size: 79KB, Performance: 100 (FCP: 1.4s), Lines of Code: 31.

All code shown in this blog post is available on GitHub, including the React.js and Dart implementations, for comparison with the Java implementations.