Skip to content

WebImage: JVM to WebAssembly

Compile Java or Kotlin to a WebAssembly module and JavaScript loader with Elide's WebImage artifact: prerequisites, backends, hosts and compiler options.

On this page

A WebImage artifact compiles JVM bytecode to a JavaScript loader plus a WASM module. To run an existing WASM or WASI binary, see WebAssembly.

Prerequisites

  • Binaryen 119 or later, with wasm-as on PATH at build time.
  • Code that meets the Native Image requirements: reachability metadata for reflection, resources and proxies; no native dependencies.
  • A host with WASM exception handling (and WASM GC for wasm-gc): Node.js 25+ or a current browser.

The deployed bundle needs neither Elide nor Binaryen.

Build a complete project

hello-wasm/
  elide.pkl
  src/main/java/Hello.java
public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello from WebImage");
  }
}
amends "elide:project.pkl"
import "elide:Jvm.pkl" as jvm
import "elide:NativeImage.pkl" as nativeImage

name = "hello-wasm"

artifacts {
  ["app-jar"] = new jvm.Jar {
    sources { "main" }
  }
  ["web"] = new nativeImage.WebImage {
    from { "app-jar" }
    name = "hello"
    entrypoint = "Hello"
    backend = "wasm-gc"
    runtime = "generic"
    autoRun = true
    sourceMap = false
    options {
      optimization = "b"
    }
  }
}
elide build

Elide builds app-jar, then compiles it to web. name is the basename without .js or .wasm; optimization = "b" is quick-build mode. Output goes to .dev/artifacts/native-image/:

FilePurpose
hello.jsJavaScript loader and entrypoint.
hello.js.wasmWASM module, loaded by hello.js.
hello.js.watWASM text form, for inspection.
hello.js.mapSource map for the loader, when sourceMap = true.

Deploy hello.js and hello.js.wasm together. Kotlin and other JVM languages work the same way once packaged in a JAR. A cache hit restores the whole bundle; turning sourceMap off removes the old .js.map.

Choose a backend and host

PropertyValueMeaning
backend"wasm-gc" (default)Uses the host’s WASM garbage collector.
backend"wasm-lm"Uses a garbage collector in linear memory; still needs the loader, not a WASI binary.
runtime"generic" (default)Loader supports browsers and Node.
runtime"browser"Loader specialized for browsers.
runtime"node"Loader specialized for Node-compatible APIs.
autoRuntrue (default)Starts the program when the loader loads; false leaves startup to the embedding page.
sourceMapfalse (default)true emits hello.js.map.

An entrypoint is required even with autoRun = false. No runtime targets Cloudflare Workers, Fastly Compute or WASI. Shared fields (from, entrypoint, moduleName, options) are listed under Native Image. A WebImage cannot be the input to a container artifact.

Target guides: Browser WASM and Edge WASM.

Run on a backend host

With runtime = "generic" or "node":

node .dev/artifacts/native-image/hello.js
Hello from WebImage

Run in a browser

With runtime = "browser" or "generic" and autoRun = true, put this index.html in .dev/artifacts/native-image/:

<!doctype html>
<html lang="en">
  <meta charset="utf-8" />
  <title>Hello WebImage</title>
  <body>
    <p>Open the browser console to see the application output.</p>
    <script src="./hello.js"></script>
  </body>
</html>
elide dev --no-tui --port 8000 .dev/artifacts/native-image

Open http://localhost:8000/; the output appears in the browser console. file:// URLs do not work.

Compiler options

The artifact’s options block takes the Native Image compiler options; which apply depends on the backend. Two cases specific to WebImage:

  • options.driverMode = "external" uses native-image from GRAALVM_HOME or JAVA_HOME; that installation must support the WebAssembly backends.
  • -H:EntryPointsConfig=config/entrypoints.json in options.flags sets entry points; the path is relative to the project root, and editing the file triggers a rebuild.

Compile without a project manifest

elide javac -- -d classes src/main/java/Hello.java
elide native-image -- --tool:wasm-gc -Ob -cp classes -o hello Hello
elide native-image -- --tool:wasm-lm -Ob -cp classes -o hello-lm Hello

Bundles are written to the current directory. A NativeImage artifact with --tool:wasm-gc or --tool:wasm-lm in options.flags still builds. To migrate it, change the class to WebImage, drop the flag and any type, and set backend.

Troubleshooting

SymptomFix
wasm-as not foundInstall Binaryen and put it on the build’s PATH.
External driver rejects the WebAssembly backendPoint GRAALVM_HOME or JAVA_HOME at an installation whose native-image supports it, or use driverMode = "embedded".
Conflicting raw optionsRemove backend, runtime, startup and source-map flags; use the typed properties. Do not override Backend or the launcher property.
Host rejects WASM instructionsUse a host with exception handling and, for wasm-gc, WASM GC.
Browser cannot load the moduleServe loader and module together over HTTP; check the requested URL.
elide run hello.js.wasm reports missing importsRun hello.js instead; the module needs its loader.

Was this page helpful?