This demo illustrates the use of Web Image - an experimental backend for GraalVM Native Image that compiles a Java application ahead-of-time and produces a WebAssembly (Wasm) module with a JavaScript wrapper. Then it can be run in browsers, Node.js, or on the GraalJS-based Node runtime.
The key idea is to show how you can currently call Java methods directly from JavaScript without relying on the main() method.
This demo exposes a simple Java method to the global JavaScript scope using the @JS annotation from the Annotation Interface.
Note: Web Image is an experimental technology and under active development. APIs, tooling, and capabilities may change.
- An Early Access build of Oracle GraalVM 25 (25e1) or later.
- All prerequisites required for Native Image building.
- Binaryen toolchain version 119 or later, available on the system path. Web Image uses
wasm-asfrombinaryenas its assembler.- macOS: It is recommended to install Binaryen using Homebrew, as the pre-built binaries from GitHub may be quarantined by the operating system:
brew install binaryen
- Other platforms: Download a pre-built release for your platform from GitHub.
- macOS: It is recommended to install Binaryen using Homebrew, as the pre-built binaries from GitHub may be quarantined by the operating system:
-
Compile the Java source file:
javac Adder.java
-
Compile the application to WASM by passing the
--tool:svm-wasmoption (it should be the first argument):native-image --tool:svm-wasm -H:-AutoRunVM Adder
The
-H:-AutoRunVMoption prevents the JVM from startingmain()automatically. CallingGraalVM.rundirectly allows you to execute code only after themainmethod finished andglobalThis.adder()is guaranteed to be available.The build produces the following artifacts in the working directory:
- adder.js - a JavaScript runtime wrapper;
- adder.js.wasm- the compiled WebAssembly module containing Java code and runtime elements (object layout, parts of Substrate VM adapted for Wasm);
- adder.js.wat - debug artifacts to understand how Java code and runtime components are lowered to WebAssembly.
-
Run the application in a browser using a simple HTTP server (with Python or Java):
python3 -m http.server 8000
jwebserver -p 8000
-
Navigate to http://localhost:8000 in the browser. Enter some numbers, click Add and see the result displayed.
What actually happens? This is the Java source code:
import java.util.function.BiFunction;
import org.graalvm.webimage.api.JS;
import org.graalvm.webimage.api.JSNumber;
public class Adder {
public static int add(int a, int b) {
return a + b;
}
@JS(args = {"adder"}, value = "globalThis.adder = adder;")
private static native void export(BiFunction<JSNumber, JSNumber, JSNumber> adder);
public static void main(String[] args) {
export((a, b) -> {
return JSNumber.of(add(a.asInt(), b.asInt()));
});
}
}@JSannotation is part of GraalVM Web Image API. It allows you to bridge Java and JavaScript.args = {"adder"}tells GraalVM that the BiFunction you pass in Java will be available as a JavaScript variableadder(not necessary if the Java source code is compiled with the-parametersoption).value = "globalThis.adder = adder;"is a raw JavaScript code executed when the export happens; it sets a variable calledadderto be globally accessible in browsers.
Further down you see the export method:
export((a, b) -> {
return JSNumber.of(add(a.asInt(), b.asInt()));
});- A lambda passed in the
exportmethod converts JS numbers (JSNumber) to Java integers, calls theaddmethod, and converts the result back toJSNumber. Whenexportis called, GraalVM runs the@JSsnippet. This makes the lambda directly callable from JS asglobalThis.adder(...).
The next part is calling from JavaScript in HTML, which happens in this part of index.html:
<script>
GraalVM.run([]).then(() => {
...
addButton.addEventListener("click", () => {
const a = parseInt(document.getElementById("num1").value);
const b = parseInt(document.getElementById("num2").value);
// Call the Java add function via WebAssembly
const result = globalThis.adder(a, b);
output.innerText = `Result: ${result}`;
});
...
});
</script>GraalVM.run([], {})initializes the Wasm module and the Java runtime inside the browser.globalThis.adder(a, b)calls theaddfunction you exported via WebAssembly, after the runtime is ready.
The focus of this demo is to demonstrate direct interaction between Java and JavaScript in the browser via WebAssembly. Note that the GraalVM Web Image API is still under active development, there will be better ways to export Java methods to JavaScript.