Skip to content

tegmentum/webassembly4j

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WebAssembly4J

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 Chicory 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
chicory4j-provider Chicory (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("chicory")  // "wasmtime", "wamr", "graalwasm", or "chicory"
    .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).

License

Apache License 2.0

About

A unified Java API for executing WebAssembly across multiple runtimes (Wasmtime, WAMR, GraalWasm, Chicory) — write once, swap engines without changing code.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages