How to run Java in the browser with WebAssembly

Java WebAssembly tutorial

WebAssembly was created to perform the highly complex and overwhelmingly sophisticated computations that push JavaScript beyond its browser-based capabilities. It is invoked by JavaScript whenever a helping hand is needed.

WebAssembly logic can be coded in any language that can be compiled into a WebAssembly virtual instruction set file. That means, with the help of WebAssembly, your browser can now execute code written in a variety of languages, including:

  • Scala
  • C/C++
  • Rust
  • GoLang
  • Kotlin
  • Clojure
  • C#
  • and most importantly, Java

W3C, WebAssembly and WASM

Furthermore, WebAssembly (WASM) is a W3C standard supported by all major browsers, including Chrome, Safari, Firefox and Edge. You don’t need to install third-party tools to use it. With WebAssembly, you can run Java in the browser without installing a Mozilla plugin or fighting with a Java applet.

All you need to do is compile your Java code into a WASM binary file, and the browser takes care of the rest.

Java, WebAssembly and the browser

In this tutorial, I will show you how to write logic in Java that compiles into a WebAssembly file and is invoked by JavaScript inside a web browser.

To drive the learning, we will build a Number Guessing Game that offloads complex computational workloads to WebAssembly for processing.

The pretend problem

Let’s pretend the JavaScript based selection of a random number caused a performance issue with our application.

To rectify this issue, we will write a Java class to perform this function instead. The Java class will compile into a WebAssembly binary that gets invoked by JavaScript, rather than make JavaScript do the cumbersome processing itself.

Java and WebAssembly Tutorial

We will offload random number generation to a Java component.

Steps to run Java in the browser with WASM

To run Java in the browser with WebAssembly, follow these basic steps:

  1. Use a WebAssembly compatible compiler and API to build your Java code.
  2. Write a Java method that contains your business logic.
  3. Add a native method reference to your Java code to interact with JavaScript.
  4. Write a JavaScript method to handle any data returned from the Java program.
  5. Use the WebAssembly API to invoke your Java code from JavaScript.
  6. Compile your Java code into a WebAssembly WASM file.
  7. Package and deploy your HTML and WASM files together and view your webpage.

WebAssembly compatible compiler and API

To run Java in the browser, you must compile your code into a WebAssembly binary file. There are a number of APIs that help you do that, including:

  • CheerpJ
  • JWebAssembly
  • TeaVM

For this example we will use TeaVM. It is well-documented, and has the easiest-to-find examples at the time of this writing.

To include TeaVM WebAssembly capabilities that allow Java to run in the browser with WASM, you’ll need to establish the following Maven configurations:

  • A dependency on teavm-classlib Java API for WebAssembly
  • A reference to the teavm-maven-plugin to compile Java to WASM

You can find the full Maven pom.xml file to support the Java WASM application on GitHub.

The WebAssembly Java code

The Java code has two main parts:

  1. An @Export annotated method to be invoked through JavaScript
  2. A native, @Import annotated method to pass data to a JavaScript method
package com.mcnz.wasm;
import  org.teavm.interop.*;

public class ComplexLogic {

  /* This method is invoked by the browser. */
  @Export(name = "getMagicNumber")
  public static void getMagicNumber(int range) {
    /* Pretend this is intense, complex logic. */
    int magicNumber = (range/2) + (range % 3);
    setMagicNumber(magicNumber);
  }

  /* This method maps to a JavaScript method in a web page. */
  @Import(module = "env", name = "setMagicNumber")   
  private static native void setMagicNumber(int message);

}

When you run the mvn package command to build the Java application, this Java source code compiles into a file named complexlogic.wasm and is placed in a subfolder named /wasm. You can change these settings through the pom.xml configuration.

The WebAssembly JavaScript code

To invoke the getMagicNumber method of the Java class, use the following JavaScript method:

WebAssembly.instantiateStreaming(
   fetch('wasm/complexlogic.wasm'), {env: {setMagicNumber}})
     .then(module => { 
         console.log("Calling the Java method from JavaScript.")
         module.instance.exports.getMagicNumber(10)
});

This JavaScript call to the WebAssembly API does three things:

  1. downloads the complexlogic.wasm file from the web browser;
  2. tells the Java program the callback method is named setMagicNumber; and
  3. invokes the getMagicNumber Java method through WebAssembly.

JavaScript WebAssembly callback method

The JavaScript callback method named setMagicNumber is below:

let magicNumber = 0;

function setMagicNumber(number) {
  console.log("Java code has called the JS setMagicNumber method.")
  magicNumber = number
  console.log("WASM set the magic number to: " + magicNumber)
}

Full code for the webpage with WebAssembly and Java

The full code for the Number Guesser Game HTML page is below:

<html>
  <head>
    <title>Java in the Browser with WebAssembly</title>
  </head>
  <body>

    <p>Guess the number between 1 and 10:</p>
    <button type="button" onclick="playTheGame(5)">5</button>
    <button type="button" onclick="playTheGame(6)">6</button>
    <button type="button" onclick="playTheGame(7)">7</button>

 <script>
  let magicNumber = 0;

  function playTheGame(number) {
    if (number == magicNumber) {
      alert("You guessed right! It is " + magicNumber);
    } else {
      alert("Wrong! Guess again!");
    }
  }

  function setMagicNumber(number) {  
    console.log("Java code has called the JS setMagicNumber method.")  
    magicNumber = number  
    console.log("WASM set the magic number to: " + magicNumber) 
  }

  WebAssembly.instantiateStreaming(  
    fetch('wasm/complexlogic.wasm'), {env: {setMagicNumber}})  
      .then(module => {   
        console.log("Calling the Java method from JavaScript.")  
        module.instance.exports.getMagicNumber(10) 
      }
  );
  </script>

</body>
</html>

Java WebAssembly deployment

To prepare the application for deployment, run the Maven package command:

mvn package

The Maven POM is configured so that the package command generates two files for deployment:

  1. the index.html that represents the webpage, and
  2. a file named complexlogic.wasm in the /wasm subfolder.

Run the Java WebAssembly application

The easiest way to deploy and test the application is to use a Java Maven Docker image.

To run the application with the source code from GitHub, use these commands:

  • git clone https://github.com/cameronmcnz/java-in-the-browser.git
  • cd java-in-the-browser
  • docker run --rm -it -p 8080:8080 -v ${pwd}:/src maven:3-jdk-8 /bin/bash
  • cd src
  • mvn package

After the maven package command runs, the application will be available on http://localhost:8080.

And that’s how easy it is to invoke code from Java in the browser with JavaScript and WebAssembly.

Java WebAssembly tutorial code is on GitHub

The source code for this Java in the browser with WebAssembly tutorial is available on GitHub.

The pom.xml file is especially long and worth reviewing if you recreate this WebAssembly tutorial with different Java class and package names.