Wasmtime Adds GC and Exception Handling: A New Era for WebAssembly Runtimes

Introduction

Imagine building a cloud-native microservice in Kotlin, compiling it to WebAssembly, and running it in a lightweight sandbox – complete with automated garbage collection and proper try-catch error handling. That future is no longer hypothetical. The Bytecode Alliance has just announced that Wasmtime, the leading WebAssembly runtime, now fully supports garbage collection (GC) and exception handling. This is a tectonic shift for the WebAssembly ecosystem, which has long struggled with the absence of these features.

WebAssembly was originally designed as a low-level compile target for C, C++, and Rust – languages that manage memory manually and rarely use structured exceptions. But the world wants more. Developers of high-level languages like Kotlin, Java, C#, and even Python have been clamoring for a runtime that can run their code safely and efficiently without forcing them to abandon managed memory or exception semantics. Wasmtime's new capabilities directly answer that call.

The Problem: WebAssembly’s Missing Pieces

For years, WebAssembly suffered from two glaring omissions:

  • No garbage collection – All memory management had to be manual. Languages with built-in GC, such as Java or Kotlin, either needed a custom allocator or relied on tricky workarounds like Emscripten’s emmalloc or dlmalloc. This added complexity and often killed performance.
  • No structured exception handlingtry-catch blocks were impossible. Developers had to simulate exceptions using setjmp/longjmp or return codes, which broke the natural flow of high-level languages and introduced subtle bugs.

These limitations forced runtime authors to build their own garbage collectors on top of WebAssembly’s linear memory – a fragile, inefficient approach. The Wasmtime team recognized that to make Wasm a universal target for modern languages, they needed native support for both GC and exceptions.

The Solution: GC and Exceptions in Wasmtime

In the July 2026 announcement, the Wasmtime developers detailed their implementation of two key WebAssembly proposals:

  • GC proposal (officially the Reference Types and GC extensions)
  • Exception handling proposal

Let’s break down what each means in practice.

Garbage Collection: Reference Types, Structs, and Arrays

The GC proposal introduces first-class support for heap-allocated objects with built-in tracing garbage collection. The core primitives are:

  • i31ref – a 31-bit integer stored inline, avoiding heap allocation for small values.
  • (ref null $t) – nullable references that can point to structs, arrays, or functions.
  • Structs ((struct (field i32) ...)) – simple, typed record types that can be allocated on the GC heap.
  • Arrays ((array (mut i8))) – dynamically sized, mutable arrays.

Wasmtime’s GC implementation follows the standard WebAssembly GC specification. The runtime tracks references, performs incremental or stop-the-world collections as needed, and allows multiple heaps per instance. This means a Kotlin object graph, a Java ArrayList, or a C# List<string> can be represented directly in Wasm without manual memory management.

A critical design choice: the GC is precise, not conservative. The runtime knows exactly which slots contain references, so it never confuses an integer with a pointer. This eliminates false retentions and ensures predictable memory use – a boon for latency-sensitive server workloads.

Exception Handling: Try, Catch, and Tags

The exception handling proposal adds two instructions: try and catch. Exceptions are represented as tags – typed entities that carry a payload and can be thrown and caught across function boundaries. The syntax is clean:

(try $try
  (do
    (call $risky-function)
  )
  (catch $my-tag
    (drop)
    ;; handle exception
  )
)

Wasmtime implements this by lowering exceptions to stack unwinding and tag matching. The runtime can delegate to the host environment or propagate exceptions into nested areturn. For languages like Kotlin that rely heavily on exceptions, this is transformational. No more Result<T, E> boilerplate or longjmp hacks – real try-catch works natively.

Feature Before Wasmtime GC/Exceptions After Wasmtime GC/Exceptions
Memory management Manual (malloc/free) or custom GC Built-in precise GC
Error handling Return codes, longjmp Structured try-catch with tags
Language support C, C++, Rust only Kotlin, Java, C#, Swift, more
Performance overhead High (custom GC slows down) Competitive (native GC, JIT optimization)

Real-World Impact: A Case Study

To understand the significance, consider a hypothetical – but realistic – scenario. A developer wants to deploy a Kotlin microservice that parses JSON and writes to a database. Before Wasmtime’s update, they had two options:

  1. Compile via Kotlin/Native to a standalone executable, losing the portability and sandboxing of Wasm.
  2. Use TeaVM or similar to compile Kotlin to JavaScript, then run it on Node.js – but that sacrifices security and performance.

With Wasmtime’s GC and exceptions, the developer can now:

  • Compile Kotlin to WebAssembly using the Kotlin/Wasm backend (now in active development).
  • Deploy the resulting .wasm module to a Wasmtime-based serverless platform.
  • Use the Kotlin standard library’s try-catch and kotlinx.serialization seamlessly – the GC handles the JSON parse tree, exceptions propagate correctly.

The result? A secure, fast microservice with minimal footprint, running in a sandbox that is both polyglot and platform-agnostic. The Bytecode Alliance article highlights early tests showing that GC-based memory management adds only negligible overhead compared to hand-tuned manual allocators, while greatly reducing developer burden.

Another concrete use case: running .NET code via the experimental dotnet-wasi toolchain. Previously, .NET’s GC had to be ported to run on Wasm’s linear memory – a huge engineering effort that also suffered from poor cache locality. With native Wasm GC, the .NET runtime can delegate its entire garbage collection to the host, leading to faster execution and simpler maintenance.

Technical Deep Dive: How Wasmtime Makes It Work

Wasmtime’s GC implementation is built on top of its Cranelift JIT compiler. When a GC object is allocated, the runtime inserts write barriers to keep the traced heap consistent. The GC itself is a generational, copying collector with concurrent sweep phases – designed to minimize pause times for interactive applications.

Exception handling is implemented via stackmaps and landing pads. During JIT compilation, the runtime records the location of every catch handler. When a tag is thrown, the engine scans the stack for a matching handler, unwinds frames, and delivers the exception – exactly like a native runtime.

Wasmtime also provides a rich API for host interactions. Host functions can throw Wasm exceptions, and Wasm exceptions can be caught by the host. This bidirectional flow is crucial for embedding Wasm into existing applications (e.g., a game engine that wants to recover from a script crash).

Challenges and Future Directions

The Wasmtime team didn’t sugarcoat difficulties. GC and exceptions add complexity to the runtime:

  • Memory overhead: Each GC’d object carries a header with type info and GC flags. For simple structs, this can increase memory usage by 20–30% compared to manual layout. However, that’s often offset by denser packing and fewer allocations.
  • Performance tuning: The GC’s generational collector works well for short-lived objects typical in request/response services, but long-running applications with large heaps may still need tuning. Wasmtime exposes GC parameters (heap size, scavenge threshold) via configuration flags.
  • Compilation speed: The JIT compiler must now handle new wasm opcodes and generate GC write barriers, which slightly increases initial compilation time. For serverless cold starts, Wasmtime is exploring pre-compiled snapshots that include GC metadata.

Looking ahead, the Bytecode Alliance article teases integration with the Component Model, which would allow components written in different languages to share GC objects safely – enabling, for instance, a Kotlin component to pass a List<String> directly to a Rust component without serialization.

Conclusion

Wasmtime’s addition of native garbage collection and exception handling is not an incremental improvement – it’s a foundational upgrade. For the first time, developers can compile high-level, managed languages to WebAssembly and run them on a production-grade runtime without sacrificing performance or ergonomics.

If you’re building serverless functions, edge computing agents, or embeddable scripting environments, now is the time to revisit WebAssembly. Language barriers are falling. Wasmtime has just made the Wasm ecosystem significantly more compelling for all languages.

Read the full announcement and technical details on the Bytecode Alliance blog: Source

← All posts

Comments