1. Overview

TeaVM is a powerful tool for translating Java bytecode into JavaScript, enabling Java applications to run directly in the browser. This allows us to maintain a Java-based codebase while targeting web environments.

In this tutorial, we’ll explore how to bridge the gap between Java and JavaScript using TeaVM’s ability to interact with the DOM and call Java methods from JavaScript.

2. Main Uses of TeaVM

When we have complex Java-based logic, it’s not practical to rewrite the same functionality for the web. TeaVM allows us to avoid redundancy by efficiently compiling our Java code into JavaScript, optimizing the size and performance of the final script.

Either way, let’s remember that our Java code must fit within the confines of the Java Class Library (JCL) emulation. For example, Swing and JavaFX aren’t supported.

2.1. Single-Page Application (SPA)

TeaVM can be the right solution for creating a Single-Page Application (SPA) from scratch almost entirely in Java and CSS, minimizing the need to write JavaScript and HTML code. This is because TeaVM has these web-specific features:

  • Call Java methods directly from JavaScript, as we’ll see in the upcoming examples
  • Create native Java methods implemented in JavaScript using the @JSBody annotation
  • Manipulate web elements with Java using the JSO library

However, TeaVM doesn’t completely replace the need for JavaScript or HTML, especially with regard to layout and some browser-specific logic. In addition, TeaVM doesn’t handle CSS, which we have to write manually or manage through external frameworks.

2.2. Existing Website

TeaVM is also great for adding features written in Java to an existing website developed with a traditional CMS like WordPress or Joomla. For example, we can create a PHP plugin for our CMS that exposes functionality to TeaVM via a REST API and include a JavaScript compiled by TeaVM on a page of our website.

Such a script, taking advantage of our REST API, can convert JSON to Java objects and vice versa, and execute our business logic written in Java. It can also modify the DOM to create a user interface or integrate into an existing one.

This use case makes sense when the client-side business logic is so complex that we are more comfortable, skilled, and familiar with Java code than with JavaScript.

2.3. Other Cases

In addition, TeaVM has WebAssembly support under active development, with a new version in the works that will allow our JavaScript-targeted applications to be ported to WebAssembly with minimal changes. However, this feature is still in an experimental phase and not yet ready.

TeaVM can also transpile Java to C. We can use this functionality as an ultra-lightweight subset of GraalVM native images. Even if this functionality is stable and ready to use, we won’t go into it because it’s beyond the main use of TeaVM.

3. Maven Setup and TeaVM Configuration

First, the current 0.10.x version of TeaVM supports Java bytecode up to JDK 21 and requires at least Java 11 to run its Java-to-JavaScript compiler. Of course, these requirements may change in later versions.

We’ll use Maven and keep the pom.xml minimal by configuring TeaVM programmatically within a Java class. This approach allows us to dynamically change the configuration based on the parameters passed to the main(…) method via Maven’s -Dexec.args option. This helps to generate different JavaScript outputs from the same project for different purposes, all while sharing the same codebase.

If we prefer an alternative method, or if Maven isn’t in use, the official TeaVM Getting Started guide provides further instructions.

3.1. pom.xml

After checking what the latest version of TeaVM is in the Maven repository, let’s add the dependencies:

<dependency>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-core</artifactId>
    <version>0.10.2</version>
</dependency>
<dependency>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-classlib</artifactId>
    <version>0.10.2</version>
</dependency>
<dependency>
    <groupId>org.teavm</groupId>
    <artifactId>teavm-tooling</artifactId>
    <version>0.10.2</version>
</dependency>

Adding these three dependencies automatically adds other TeaVM transitive dependencies. The teavm-core includes libraries such as teavm-interop, teavm-metaprogramming-api, relocated ASM libraries (for bytecode manipulation), HPPC, and Rhino (JavaScript engine). In addition, teavm-classlib brings in teavm-jso, commons-io, jzlib and joda-time. The teavm-tooling dependency also includes a relocated version of commons-io.

These transitive dependencies provide essential functionality without the need to add them manually.

3.2. TeaVMRunner.java

This class configures TeaVM, we need to specify it using Maven’s -Dexec.mainClass option:

public class TeaVMRunner {
    public static void main(String[] args) throws Exception {
        TeaVMTool tool = new TeaVMTool();
        tool.setTargetDirectory(new File("target/teavm"));
        tool.setTargetFileName("calculator.js");
        tool.setMainClass("com.baeldung.teavm.Calculator");
        tool.setTargetType(TeaVMTargetType.JAVASCRIPT);
        tool.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
        tool.setDebugInformationGenerated(false);
        tool.setIncremental(false);
        tool.setObfuscated(true);
        tool.generate();
    }
}

Let’s take a closer look:

  • setTargetDirectory(…) → Output directory where TeaVM will place the generated files
  • setTargetFileName(…) → Name of the generated JavaScript file
  • setMainClass(…) → Fully qualified name of the main class of the application
  • setTargetType(…) → JavaScript, WebAssembly, or C
  • setOptimizationLevel(…) → The ADVANCED level produces the smallest JavaScript files, even smaller than the FULL level
  • setDebugInformationGenerated(…) → Only useful for creating a debug information file for the TeaVM Eclipse plugin
  • setIncremental(…) → If enabled, compiles faster, but reduces optimizations, so it’s not recommended for production
  • setObfuscated(…) → It reduces the size of the generated JavaScript by two or three times when enabled, so it should be preferred in most cases

There are also other options documented in the Tooling section of the official guide, e.g. for generating source maps.

The most important thing to note is that using setMainClass(…)* creates a JavaScript main() function with global scope that executes the translated version of the Java *main(String[]). We’ll see an example of this later.

3.3. HTML Page

While simple, this is a complete example of how to include the Javascript file generated by TeaVM. We’ve left out meta tags to optimize for mobile devices, indexing, and other requirements unrelated to TeaVM:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>TeaVM Example</title>
    <script type="text/javascript" src="calculator.js"></script>
    <script type="text/javascript">
        document.addEventListener('DOMContentLoaded', function() {
            // Call JS functions here
        });
    </script>
</head>
<body>
<h1>TeaVM Example</h1>
<div id="calculator-container"></div>
</body>
</html>

In this example, calculator.js is the file we specified earlier in setTargetFileName(…). We’ll see what to put in place of the “Call JS functions here” comment in a moment. Finally,

is a placeholder that we’ll use to create a sample calculator.

4. Calculator Example

Let’s take a simple addition example.

4.1. Calling a Java Method From Javascript

This is the class we previously specified in setMainClass(…):

public class Calculator {

    public static void main(String[] args) {
    }

    @JSExport
    public static int sum(int a, int b) {
        return a + b;
    }
}

The @JSExport annotation in TeaVM is used to make Java methods, fields, or classes accessible to JavaScript, allowing them to be called directly from JavaScript code. So, after compiling the code, let’s call the sum(…) function in our example HTML page:

[...]
document.addEventListener('DOMContentLoaded', function() {
    // Call JS functions here
    let result = sum(51, 72);
    console.log("Sum result: " + result);
[...]

This is the output of the browser’s JavaScript console:

Sum result: 123

The result is as expected.

4.2. Java-Based DOM Manipulation

Now let’s implement the main(…) function. Let’s notice that document.getElementById(“calculator-container”) goes to select the

tag that we previously inserted in the example HTML file:

public static void main(String[] args) {
    HTMLDocument document = HTMLDocument.current();
    HTMLElement container = document.getElementById("calculator-container");

    // Create input fields
    HTMLInputElement input1 = (HTMLInputElement) document.createElement("input");
    input1.setType("number");
    container.appendChild(input1);

    HTMLInputElement input2 = (HTMLInputElement) document.createElement("input");
    input2.setType("number");
    container.appendChild(input2);

    // Create a button
    HTMLButtonElement button = (HTMLButtonElement) document.createElement("button");
    button.appendChild(document.createTextNode("Calculate Sum"));
    container.appendChild(button);

    // Create a div to display the result
    HTMLElement resultDiv = document.createElement("div");
    container.appendChild(resultDiv);

    // Add click event listener to the button
    button.addEventListener("click", (evt) -> {
        try {
            long num1 = Long.parseLong(input1.getValue());
            long num2 = Long.parseLong(input2.getValue());
            long sum = num1 + num2;
            resultDiv.setTextContent("Result: " + sum);
        } catch (NumberFormatException e) {
            resultDiv.setTextContent("Please enter valid integer numbers.");
        }
    });
}

The code is self-explanatory. In a nutshell, it dynamically creates input fields, a button, and a result display area within the web page, all using Java. The button listens for a click event, retrieves the numbers entered in the input fields, calculates their sum, and displays the result. If invalid input is provided, an error message is shown instead.

Let’s remember to call the main() function:

document.addEventListener('DOMContentLoaded', function() {
    // Call JS functions here
    main();
});

Here is the result:

TeaVM Example Calculator

Starting with a simple example like this, we can modify the DOM as we like to create any layout, even with the help of CSS.

5. Conclusion

In this article, we explored how TeaVM facilitates the translation of Java bytecode into JavaScript, allowing Java applications to run directly in web browsers. We covered key features such as how to call Java methods from JavaScript, perform Java-based DOM manipulation, and implement simple web applications without having to write extensive JavaScript code. Through a practical calculator example, we demonstrated the ease of bridging Java and web development using TeaVM.

As always, the full source code is available over on GitHub.