WebAssembly4J
June 30, 2026 ยท View on GitHub
A unified Java API for executing WebAssembly across multiple runtimes.
Write your code once against a stable API and swap runtimes without changing application code. WebAssembly4J supports Wasmtime, WAMR, GraalWasm, and Endive out of the box.
Quick Start
Add the API and a provider to your project:
<dependency>
<groupId>ai.tegmentum.webassembly4j</groupId>
<artifactId>webassembly4j-runtime</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>ai.tegmentum.webassembly4j</groupId>
<artifactId>wasmtime4j-provider</artifactId>
<version>1.4.0</version>
</dependency>
Run a WebAssembly function in three lines:
byte[] wasm = Files.readAllBytes(Path.of("add.wasm"));
int result = WasmRuntime.call(wasm, "add", Integer.class, 2, 3);
// result == 5
Core API
The low-level API gives you full control over the engine lifecycle:
try (Engine engine = WebAssembly.newEngine()) {
Module module = engine.loadModule(Path.of("math.wasm"));
Instance instance = module.instantiate();
Function add = instance.function("add").orElseThrow();
Object result = add.invoke(2, 3);
}
Typed Functions
Avoid boxing overhead with typed function wrappers:
Function fn = instance.function("add").orElseThrow();
TypedFunction.I32_I32_I32 add = fn.typed(TypedFunction.I32_I32_I32.class);
int result = add.call(2, 3);
Module Introspection
Inspect a module's exports and imports before instantiation:
Module module = engine.loadModule(wasmBytes);
for (ExportDescriptor export : module.exports()) {
System.out.println(export.name() + " : " + export.type());
}
Host Functions
Provide host functions to a WebAssembly module via LinkingContext:
LinkingContext ctx = LinkingContext.builder()
.define("env", "log", new ValueType[]{ValueType.I32}, new ValueType[]{},
args -> { System.out.println("wasm says: " + args[0]); return null; })
.build();
Instance instance = module.instantiate(ctx);
High-Level Runtime
WasmRuntime provides a static facade for common operations:
// One-shot call
int result = WasmRuntime.call(wasmBytes, "factorial", Integer.class, 10);
// Interface proxy -- bind a Java interface to WASM exports
try (Calculator calc = WasmRuntime.load(Calculator.class, wasmBytes)) {
int sum = calc.add(2, 3);
}
// Pre-compile for reuse
WasmModule compiled = WasmRuntime.compile(wasmBytes);
try (WasmInstance inst = compiled.instantiate()) {
inst.call("run");
}
Modules
| Module | Description | Java |
|---|---|---|
webassembly4j-api | Stable user-facing API (Multi-Release JAR: 8/11/22) | 8+ |
webassembly4j-spi | Provider contracts and discovery | 11+ |
webassembly4j-runtime | High-level runtime with proxy binding and marshalling | 11+ |
webassembly4j-bindgen | WIT binding generator (Maven plugin + CLI) | 11+ |
webassembly4j-testing | JUnit 5 multi-engine test support | 11+ |
webassembly4j-pool | Thread-safe instance pooling | 11+ |
webassembly4j-spring | Spring Boot auto-configuration | 17+ |
webassembly4j-benchmarks | JMH benchmarks across all engines | 17+ |
Providers
| Provider | Engine | Java | Priority |
|---|---|---|---|
wasmtime4j-provider | Wasmtime (via wasmtime4j) | 11+ | 200 |
graalwasm4j-provider | GraalWasm (Polyglot API) | 17+ | 150 |
wamr4j-provider | WAMR (via wamr4j) | 17+ | 100 |
endive4j-provider | Endive (pure Java) | 11+ | 50 |
Providers are discovered automatically via ServiceLoader. When multiple providers are on the classpath, the one with the highest priority is selected. To select a specific provider, use the builder:
Engine engine = WebAssembly.builder()
.provider("endive") // "wasmtime", "wamr", "graalwasm", or "endive"
.build();
Native backend: JNI vs Panama
The wasmtime4j-provider and wamr4j-provider bundle the JNI native runtime by default, so they work out of the box on every supported Java version with no extra setup.
Both engines also offer a Panama (Java FFM) backend. It is not bundled because its classes are compiled to Java 22 bytecode, and merely having them on the classpath crashes ServiceLoader discovery on older JVMs (UnsupportedClassVersionError). To use it, add the artifact yourself โ only on Java 22+:
<!-- Wasmtime: auto-selected on Java 23+, JNI below -->
<dependency>
<groupId>ai.tegmentum</groupId>
<artifactId>wasmtime4j-panama</artifactId>
<version>46.0.1-1.1.7</version>
<scope>runtime</scope>
</dependency>
<!-- WAMR: auto-selected on Java 23+ -->
<dependency>
<groupId>ai.tegmentum.wamr4j</groupId>
<artifactId>wamr4j-panama</artifactId>
<version>2.4.4-1.0.2</version>
<scope>runtime</scope>
</dependency>
The engine's runtime factory then selects Panama automatically based on the running Java version (keep the bundled JNI as the fallback). To force a backend explicitly, set -Dwasmtime4j.runtime=panama / -Dwamr4j.runtime=Panama (or jni). On recent JDKs, add --enable-native-access=ALL-UNNAMED to silence FFM warnings.
WasmGC Object Bridge
For languages that compile to WasmGC (Kotlin/Wasm, Dart, OCaml, Java via J2Wasm), the GC bridge provides automatic marshalling between Java objects and WebAssembly GC struct instances -- no linear memory management needed.
Defining Mappable Types
Annotate a Java class or record with @GcMapped:
@GcMapped
public record Point(double x, double y) {}
@GcMapped
public record Line(Point start, Point end) {}
Supported field types: int, long, float, double, boolean, and nested @GcMapped types.
Marshalling Objects
GcExtension gc = instance.extension(GcExtension.class)
.orElseThrow(() -> new UnsupportedOperationException("GC not supported"));
GcMarshaller marshaller = GcMarshaller.forExtension(gc);
// Java -> GC struct
Point p = new Point(3.0, 4.0);
GcStructInstance struct = marshaller.marshal(p);
// GC struct -> Java
Point result = marshaller.unmarshal(struct, Point.class);
Nested @GcMapped fields are recursively marshalled as GC struct references. Null references are preserved in both directions.
GC Interface Proxy
GcProxyFactory creates interface proxies that automatically marshal @GcMapped parameters and return values through GC structs:
interface Geometry extends AutoCloseable {
@WasmExport("rotate_point")
Point rotate(Point p, double angle);
@WasmExport("distance")
double distance(Point a, Point b);
}
GcExtension gc = instance.extension(GcExtension.class).orElseThrow();
Geometry geom = GcProxyFactory.create(
Geometry.class, engine, module, instance, gc);
Point rotated = geom.rotate(new Point(1, 0), Math.PI / 2);
double dist = geom.distance(new Point(0, 0), new Point(3, 4));
Primitive parameters pass through directly. This is the GC counterpart to ProxyFactory which marshals through linear memory.
Low-Level GC API
For fine-grained control, use the GC extension directly:
GcStructType pointType = GcStructType.builder("Point")
.addField("x", GcFieldType.f64(), true)
.addField("y", GcFieldType.f64(), true)
.build();
GcStructInstance point = gc.createStruct(pointType,
GcValue.f64(3.0), GcValue.f64(4.0));
double x = point.getField(0).asF64();
point.setField(0, GcValue.f64(5.0));
Testing
The webassembly4j-testing module provides JUnit 5 support for running tests against every available engine:
@WasmTest
@WasmModule("math.wasm")
void addFunction(Instance instance) {
Function add = instance.function("add").orElseThrow();
assertEquals(5, add.invoke(2, 3));
}
Each test method runs once per discovered engine. The extension handles engine and instance lifecycle automatically.
Instance Pooling
For high-throughput scenarios, pool and reuse instances:
PoolConfig config = PoolConfig.builder()
.minSize(2)
.maxSize(16)
.build();
try (WasmInstancePool pool = WasmInstancePool.create(wasmBytes, config)) {
try (PooledInstance inst = pool.borrow()) {
inst.function("handle").orElseThrow().invoke(request);
} // instance returned to pool on close
}
Spring Boot Integration
Add webassembly4j-spring and a provider to your Spring Boot application:
<dependency>
<groupId>ai.tegmentum.webassembly4j</groupId>
<artifactId>webassembly4j-spring</artifactId>
<version>1.4.0</version>
</dependency>
An Engine bean is auto-configured and available for injection. Configuration properties:
webassembly4j.enabled=true
webassembly4j.engine=wasmtime
Health indicator and actuator endpoint (/actuator/wasm) are registered when Spring Boot Actuator is present.
Bindgen
Generate Java bindings from WIT (WebAssembly Interface Types) definitions:
<plugin>
<groupId>ai.tegmentum.webassembly4j</groupId>
<artifactId>webassembly4j-bindgen</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
Supports both modern (Java 17+ records) and legacy (Java 8+ POJOs) code styles.
Building
./mvnw clean install
The API module targets Java 8+ as a Multi-Release JAR (8/11/22). Core modules require Java 11+. The full build including all providers requires JDK 22+ (for the MRJAR Java 22 overlay compilation).