|
| 1 | +# Web Image: Export Java Method Example |
| 2 | + |
| 3 | +This demo illustrates the use of **Web Image** - an experimental backend for [GraalVM Native Image](https://www.graalvm.org/latest/reference-manual/native-image/) that compiles a Java application ahead-of-time and produces a WebAssembly (Wasm) module with a JavaScript wrapper. |
| 4 | +Then it can be run in browsers, Node.js, or on the [GraalJS-based](https://github.com/oracle/graaljs/tree/master/graal-nodejs) Node runtime. |
| 5 | + |
| 6 | +The key idea is to show how you can currently **call Java methods directly from JavaScript** without relying on the `main()` method. |
| 7 | +This demo exposes a simple Java method to the global JavaScript scope using the `@JS` annotation from the [Annotation Interface](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/JS.html). |
| 8 | + |
| 9 | +> Note: Web Image is an experimental technology and under active development. APIs, tooling, and capabilities may change. |
| 10 | +
|
| 11 | +## Prerequisites |
| 12 | + |
| 13 | +* An [Early Access build](https://github.com/graalvm/oracle-graalvm-ea-builds/releases) of Oracle GraalVM 25 (25e1) or later. |
| 14 | +* All [prerequisites](https://www.graalvm.org/latest/reference-manual/native-image/#prerequisites) required for Native Image building. |
| 15 | +* [Binaryen toolchain](https://github.com/WebAssembly/binaryen) version 119 or later, available on the system path. Web Image uses `wasm-as` from `binaryen` as its assembler. |
| 16 | + * **macOS**: It is recommended to install Binaryen using Homebrew, as the pre-built binaries from GitHub may be quarantined by the operating system: |
| 17 | + ```bash |
| 18 | + brew install binaryen |
| 19 | + ``` |
| 20 | + * **Other platforms**: Download a pre-built release for your platform from [GitHub](https://github.com/WebAssembly/binaryen/releases). |
| 21 | + |
| 22 | +## Building the WebAssembly Module |
| 23 | + |
| 24 | +1. Compile the Java source file: |
| 25 | + ```bash |
| 26 | + javac Adder.java |
| 27 | + ``` |
| 28 | +2. Compile the application to WASM by passing the `--tool:svm-wasm` option (it should be the first argument): |
| 29 | + ```bash |
| 30 | + native-image --tool:svm-wasm -H:-AutoRunVM Adder |
| 31 | + ``` |
| 32 | + The `-H:-AutoRunVM` option prevents the JVM from starting `main()` automatically. Calling `GraalVM.run` directly allows you to execute code only after the `main` method finished and `globalThis.adder()` is guaranteed to be available. |
| 33 | +
|
| 34 | + The build produces the following artifacts in the working directory: |
| 35 | + - _adder.js_ - a JavaScript runtime wrapper; |
| 36 | + - _adder.js.wasm_- the compiled WebAssembly module containing Java code and runtime elements (object layout, parts of [Substrate VM](https://github.com/oracle/graal/tree/master/substratevm) adapted for Wasm); |
| 37 | + - _adder.js.wat_ - debug artifacts to understand how Java code and runtime components are lowered to WebAssembly. |
| 38 | +
|
| 39 | +3. Run the application in a browser using a simple HTTP server (with Python or Java): |
| 40 | + ```bash |
| 41 | + python3 -m http.server 8000 |
| 42 | + ``` |
| 43 | + ```bash |
| 44 | + jwebserver -p 8000 |
| 45 | + ``` |
| 46 | + |
| 47 | +4. Navigate to [http://localhost:8000](http://localhost:8000) in the browser. Enter some numbers, click **Add** and see the result displayed. |
| 48 | + |
| 49 | +## Review the Sample Application |
| 50 | + |
| 51 | +What actually happens? This is the Java source code: |
| 52 | +```java |
| 53 | +import java.util.function.BiFunction; |
| 54 | +import org.graalvm.webimage.api.JS; |
| 55 | +import org.graalvm.webimage.api.JSNumber; |
| 56 | +
|
| 57 | +public class Adder { |
| 58 | + public static int add(int a, int b) { |
| 59 | + return a + b; |
| 60 | + } |
| 61 | +
|
| 62 | + @JS(args = {"adder"}, value = "globalThis.adder = adder;") |
| 63 | + private static native void export(BiFunction<JSNumber, JSNumber, JSNumber> adder); |
| 64 | +
|
| 65 | + public static void main(String[] args) { |
| 66 | + export((a, b) -> { |
| 67 | + return JSNumber.of(add(a.asInt(), b.asInt())); |
| 68 | + }); |
| 69 | + } |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +- `@JS` annotation is part of [GraalVM Web Image API](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/JS.html). It allows you to bridge Java and JavaScript. |
| 74 | +- `args = {"adder"}` tells GraalVM that the BiFunction you pass in Java will be available as a JavaScript variable `adder` (not necessary if the Java source code is compiled with the `-parameters` option). |
| 75 | +- `value = "globalThis.adder = adder;"` is a raw JavaScript code executed when the export happens; it sets a variable called `adder` to be globally accessible in browsers. |
| 76 | + |
| 77 | +Further down you see the `export` method: |
| 78 | +```java |
| 79 | +export((a, b) -> { |
| 80 | + return JSNumber.of(add(a.asInt(), b.asInt())); |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +- A lambda passed in the `export` method converts JS numbers (`JSNumber`) to Java integers, calls the `add` method, and converts the result back to `JSNumber`. |
| 85 | +When `export` is called, GraalVM runs the `@JS` snippet. |
| 86 | +This makes the lambda directly callable from JS as `globalThis.adder(...)`. |
| 87 | + |
| 88 | +The next part is calling from JavaScript in HTML, which happens in this part of _index.html_: |
| 89 | +```js |
| 90 | +<script> |
| 91 | +GraalVM.run([]).then(() => { |
| 92 | + ... |
| 93 | + addButton.addEventListener("click", () => { |
| 94 | + const a = parseInt(document.getElementById("num1").value); |
| 95 | + const b = parseInt(document.getElementById("num2").value); |
| 96 | +
|
| 97 | + // Call the Java add function via WebAssembly |
| 98 | + const result = globalThis.adder(a, b); |
| 99 | +
|
| 100 | + output.innerText = `Result: ${result}`; |
| 101 | + }); |
| 102 | + ... |
| 103 | +}); |
| 104 | +</script> |
| 105 | +``` |
| 106 | + |
| 107 | +- `GraalVM.run([], {})` initializes the Wasm module and the Java runtime inside the browser. |
| 108 | +- `globalThis.adder(a, b)` calls the `add` function you exported via WebAssembly, after the runtime is ready. |
| 109 | + |
| 110 | +### Conclusion |
| 111 | + |
| 112 | +The focus of this demo is to demonstrate direct interaction between Java and JavaScript in the browser via WebAssembly. |
| 113 | +Note that the [GraalVM Web Image API](https://www.graalvm.org/sdk/javadoc/org/graalvm/webimage/api/JS.html) is still under active development, there will be better ways to export Java methods to JavaScript. |
0 commit comments