Unlike the Export Java Method Example which exchanges primitive values like numbers, this demo illustrates how you can work with objects: JavaScript sends a request object, Java processes it, and returns a structured response object, via WebAssembly.
Object fields can be read directly with typed get calls.
This tiny in-browser service is compiled to WebAssembly using Web Image - an experimental backend for GraalVM Native Image that can compile a JVM application ahead-of-time and produce a Wasm module with a JavaScript wrapper. You can run this small backend service in a browser or on Node.js.
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 PricingService.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 PricingService
The
-H:-AutoRunVMoption prevents the VM from startingmain()automatically. CallingGraalVM.runfrom JavaScript ensures that the exported function is available after the runtime initializes.The build produces the following artifacts:
- pricingservice.js — JavaScript runtime wrapper
- pricingservice.js.wasm — the compiled WebAssembly module
- pricingservice.js.wat — debug artifact showing the generated WebAssembly text format
-
Run the application in a browser using a simple HTTP server:
python3 -m http.server 8000
jwebserver -p 8000
Navigate to http://localhost:8000, and click the button in the demo page to send a request object to Java and display the processed result.
This example exports a single Java method that calculates a price based on user input.
JavaScript sends a request object, Java handles it in handleRequest(...), and returns a response object.
import java.util.function.Function;
import org.graalvm.webimage.api.JS;
import org.graalvm.webimage.api.JSObject;
import org.graalvm.webimage.api.JSNumber;
public class PricingService {
@JS(args = {"handler"}, value = "globalThis.pricingService = handler;")
private static native void export(Function<JSObject, JSObject> handler);
public static void main(String[] args) {
export(PricingService::handleRequest);
}
private static JSObject handleRequest(JSObject request) {
String operation = request.get("operation", String.class);
int price = request.get("price", Integer.class);
JSObject user = request.get("user", JSObject.class);
boolean premium = user.get("premium", Boolean.class);
int discount = 0;
if ("discount".equals(operation)) {
discount = premium ? 20 : 10;
}
int finalPrice = price - (price * discount / 100);
JSObject response = JSObject.create();
response.set("finalPrice", JSNumber.of(finalPrice));
response.set("discountApplied", JSNumber.of(price - finalPrice));
response.set("premium", premium);
return response;
}
}- The
@JSannotation exposes a JavaScript code snippet to Java. It is part of GraalVM Web Image API. args = {"handler"}defines the variable name available in JavaScript.- The JavaScript snippet assigns the function to
globalThis.pricingService, and makes it callable from JavaScript once the runtime is ready.
export(PricingService::handleRequest);- JavaScript needs a callable value. With Web Image API, that callable can be an anonymous lambda, a method reference, or a named implementation class.
- In this demo, the callable is a Java method exposed via the method reference
PricingService::handleRequest. - JavaScript sends an object, Java processes it, and returns another object.
Next Java reads properties from a JavaScript request object.
With typed get operations, Java values can be read directly.
String operation = request.get("operation", String.class);
int price = request.get("price", Integer.class);
JSObject user = request.get("user", JSObject.class);
boolean premium = user.get("premium", Boolean.class);Notice also that nested objects can also be accessed.
After reading the properties, it creates a response object in Java.
This object becomes a normal JavaScript object when returned.
JSObject response = JSObject.create();
response.set("finalPrice", JSNumber.of(finalPrice));In the HTML file you see:
<script>
GraalVM.run([]).then(() => {
const button = document.getElementById("run");
const output = document.getElementById("output");
button.addEventListener("click", () => {
const priceInput = parseInt(document.getElementById("price").value, 10);
const premiumInput = document.getElementById("premium").checked;
const request = {
operation: "discount",
price: Number.isNaN(priceInput) ? 0 : priceInput,
user: {
premium: premiumInput,
},
};
const result = globalThis.pricingService(request);
output.innerText = JSON.stringify(result, null, 2);
});
});
</script>GraalVM.run([])initializes the WebAssembly runtime. The wrapper should be loaded in HTML before theGraalVM.runcall.- After initialization,
globalThis.pricingService(...)becomes available. - The Java code runs inside WebAssembly and returns the result.