And here we are again. For the third time in a row, we are back to the Java Advent, eager to discover what’s new with WebAssembly from a Java developer perspective.
Incidentally, since, as you know, I have a favorite topic (after programming languages and compilers, of course), it is also the third time in a row I have worked at a different company.
The first time I was at Red Hat, last year I was at Tetrate, and this year I joined Dylibso. Maybe Dylibso does jingle a bell (see what I just did there? Come on, it’s Java Advent), or maybe it does not. But if you have followed this award-winning series of blog posts, you might remember Dylibso for the Extism framework. Extism is an easy-to-use cross-platform framework to write plugins for several languages using the same API across languages.
To that end, we use and contribute to quite a few runtimes; for instance, we use wazero (the runtime I have been contributing to for the last year and a half at Tetrate) for our Go SDK. Dylibso started and have been sponsoring the development of the pure-Java Chicory runtime which has recently hit its first 1.0 milestone release. We’ll talk more about that later!
Finally, we have also just released a cool new product called XTP that builds and extends Extism with type-safe bindings, codegen and a plug-in delivery system! More on that later too!
Is This 🫴🦋 About Front-End?
Well, yes, and no. Have you been living under a rock or is this your first Java Advent? Just kidding.
Sure, Wasm was originally created to bring a safe, sandboxed execution environment to the browser, leveraging the existing JS runtime. But the Wasm spec is relatively smaller than the bulk of a full JS implementation, and, as such, a constellation of pure-Wasm runtimes was born: these smaller, lighter-weight runtimes can be used as stand-alone language VMs (similarly to a JVM), or as embededded language hosts, as you would usually do for a scripting language, except that they are language-agnostic.
In fact, Wasm is a language-agnostic compilation target and any compiler is free to generate it: that makes it a great binary format to distribute cross-platform software extensions (i.e. plug-ins) that can be hosted in any “host” application.
In the last few years, we have seen the CNCF cloud-native landscape extend, and promote this new technology, to the point that Wasm itself has now its own section in the CNCF software landscape. Wasm is now for all intents and purposes considered one of the CNCF official technologies for “Cloud-Native development”: this extends well-beyond the browser!
But how does this affect you, as a Java developer? Similarly to the other years, in this blog post, I will explore two main topics:
- front-end development, compiling a JVM language into Wasm
- back-end development running Wasm on top of a JVM
The Front-End
Wasm was always meant to make the Web platform more efficient. That’s one reason why browsers and state-of-the-art JavaScript runtimes such as V8, JavaScriptCore and Spidermonkey, will probably always be at the forefront, when it comes to implementing practical, but bleeding-edge features. After all, for better or worse, that’s how Web development goes.
This is also why often these runtimes adopt experimental features of the Wasm spec, way before other, smaller runtimes. This is expected, considering also the workforce that is often beyond major web browsers.
Last year we explained how Wasm behaves more like a native target, than a JVM. This is, to some extent, still true: most WebAssembly runtimes do not support much beyond the so-called “linear memory”, threading support is still hit and miss, and the exception handling spec was revised a few times, finally hitting the last stage at the end of last year.
However, today all major browsers now support garbage-collected references, exception handling, and, through some shims, even threaded execution. All of these features are particularly important when it comes to supporting higher-level, managed, garbage-collected languages such as those hosted on the Java platform.
It is indeed feasible to support these features even in a native-like compilation target: after the GraalVM Native Image builder does just that. And Go is a higher-level, managed, garbage-collected, multi-threaded language and both TinyGo and “Big” Go can now be compiled to Wasm that runs perfectly in self-contained Wasm runtimes such as Wasmtime, wazero or chicory.
However, this comes with the cost of inflating the resulting binary, with baggage such as a garbage collector, emulating exception handlers, emulating multi-threading.
I believe this situation will continue for a while; that is, smaller runtimes, especially if they want to keep their footprint small, and especially if their development team is small, will generally be very conservative when it comes to implementing new, experimental part of the specs.
But it’s good that browsers are the front-runners: they have better resourcing, and hence can afford to support these experiments earlier. And besides, it makes sense for the Web to try to keep binary sizes down. For other use cases, we will see, this is less of a concern.
In the following I will show 3 compiler toolchains that generate Wasm, but still mainly target the browser.
Feeling fatigued already? We’re just getting started!
Kotlin
Kotlin was one of the first JVM languages that targeted the browser with their JavaScript backend. Over the years, they gained a native compiler, and also, starting off as a flavor of their native compiler, a Wasm compiler.
The Kotlin team has recently switched their Wasm compiler backend to WasmGC and the Exception Handling proposal, resulting in smaller file sizes. However, this required a major overhaul of the compiler architecture, because a Wasm binary that targets the linear memory behaves more like a native target, while targeting WasmGC is a bit closer to how JVM class files are represented.
Luckily, with their familiarity with targeting both the JVM and JavaScript, even though it was no small feat, we now have a functional Kotlin compiler for Wasm that works for on all runtimes supporting the GC and Exception Handling specs.
To get started, you can head over to this Kotlin Wasm template.
I believe that the Kotlin toolchain is currently the most impressive and advanced when it comes to practical use cases for JVM language: the teams at Jetbrains have already ported Compose Multiplatform to it. This that, you can already today port complex applications that will run both on mobile platforms and in the browser, with the convenience of using one modern, familiar programming language!
Kudos to the team!
Scala
Scala was a pioneer in the JVM language ecosystem for many reasons; it helped revive the attention to the Java platform with a new, concise and practical programming language, in a time where the Java language was still very conservative.
It is also one of the first JVM languages to gain support for a JavaScript compiler.
End users have been asking for a Wasm backend for a while. But the JavaScript backend was pretty practical and battle-tested; so the team deferred this sizable effort until there was reasonable support for some key features, such as, for instance (again!) garbage collected objects.
In the meantime, the alternative Scala Native backend initially experimented with it. Now, the Scala.js team has decided to finally take on this effort, as the feature gap in browsers is closing more and more.
As documented on the Scala.js website the Wasm backend is still in its infancy, and it comes with some experimental requirements (including garbage collected references, and exception handling support). It is also primarily targeting browsers, and as such, it still emits some chunks of JavaScript.
For some examples, check out keynmol/scalajs-wasm-game-of-life and https://github.com/sjrd/funlabyrinthe-scala.
Java
I mentioned TeaVM multiple times in the past. TeaVM has recently switched their backend to WasmGC.
The J2CL project is the successor to GWT. This project is extensively used at Google, to the point that you are probably already using it even if you don’t realize it. For instance, Google Sheets is using Wasm. Check out WebAssembly at Google by Thomas Steiner & Thomas Nattestadt. And since you are there, you might want to check out Thomas Steiner’s podcast, WasmAssembly there is also an episode interviewing our very own Steve, talking about all things Extism, including Chicory!
Read more on how to use J2CL with Wasm here.
GraalVM Native Image
All the languages we have mentioned earlier, have something in common: they all generate Wasm code starting from source code. This means that the Kotlin compiler compiles Kotlin code, the Scala compiler compiles Scala code, and the TeaVM and J2CL compilers compile Java code. “Well, duh”, I hear you say.
On the other hand, GraalVM native image builds a binary starting from class files, making it possible to generate binaries for any language that typically runs on a JVM. So, considering the history of Wasm compilers so far, it would be really cool if they also started to build upon the experience they made with the Native Image builder, and implement a new backend for WebAssembly.
Oh boy, do I have news for you! It turns out that the GraalVM team is in fact working on a Wasm backend that will target browsers and state-of-the-art JavaScript runtimes such as V8 (at least at the beginning).
The work is being actively tracked on GitHub. I had the chance to take a look at an early demo, where a fully functional javac
ran in the browser. The compiler is targeting WASI too, so, you should also be able to run it with Node, and, when more will catch up, with all the other self-contained Wasm runtimes.
There is some work to do but the direction is extremely exciting!
Honorable Mentions
I am always fascinated with the excellent work at LeaningTech, so, even though it’s kind of a different stack, I want to mention the herculean work that this team is doing in porting code to the browser. Besides their very recent relaunch of their x86 in-browser VM, this time including a GUI, they also provide commercial support to compiling C++ and Flash to Wasm.
I believe one of the crown jewels is their Java runtime stack called CheerpJ, which they also recently relaunched. It is a sophisticated port of a proper OpenJDK to Wasm that JIT-compiles bytecode into Wasm for optimal performance, and contained code size. They can also run Java Applets and JNLP without a Java runtime installed on the desktop. That’s pretty neat!
Backend
Wasm in the browsers is definitely here to stay, but the thing about this ecosystem that interests me the most is Wasm workloads in the backend. Wasm is on the ThoughtWorks 2024 Tech Radar
This year a lot has happened there too. But how does it impact JVM developers?
Last year, a surprising Christmas gift for all Java developers was the first global official announcement of the Chicory interpreter. While at the time the project was in an early development stage, it was already quite capable! Last October we announced our first milestone release, including, next to the interpreter, a new experimental bytecode translator (the “AoT compiler”), that turns Wasm bytecode into JVM Wasm for improved performance. Check it out and let us all know!
The GraalVM team also recently announced that their Wasm support can be now considered stable and ready for production, with the benefit of all their past and present experience with developing languages for the JVM.
This means that it’s a great time to get started with Wasm support on the JVM. But what are the best use cases you can cover with a Wasm runtime that runs on a JVM? Isn’t this redundant? Why would you want to load a foreign bytecode format (Wasm) on a bytecode language runtime (i.e., your beloved JVM)?
There are many use cases, but I want to start from possibly the most niche and counterintuitive, because it’s cursed, and it feels wrong, and yet sometimes it makes so much sense.
Native Libraries Without JNI
“LOL look at this one,”JNI”: we’ve got Panama, now, you boomer!” Right. Of course, Panama will finally bring a saner developer experience to interfacing with a native library.
However, you still have to deal with all of the limitations of interfacing with native code; essentially,
- the garbage collector and the threads (virtual and native!) may not play along well with it;
- plus, a memory corruption bug in your native code could tear down your entire JVM.
- finally, your build is now tied to the specific CPU architectures that your native library provides support for (this may not depend on you!)
Now, it would be most excellent if you could just bring a native library to JVM land, and be done with it. Some projects attempted this before:
- NestedVM was a wonderfully cursed project that translated binaries for the MIPS architecture to Java bytecode. Unfortunately it hasn’t released a new version since 2009.
- GraalVM’s Sulong is a high-performance LLVM bitcode runtime; i.e. it evaluates LLVM’s intermediate binary representation. One downside is that LLVM bitcode is known to be unstable.
The key difference is that, this time
- 1a lot of compilers are including first-party support for Wasm, so it’s easier to cross-compile to this target and check whether it’s working.
- the Web itself is huge, and a lot of developers are interested in porting well-written, performance-conscious libraries to use in their Web application
As a consequence, it’s becoming easier to find a port of a traditionally “native” library to Wasm, and if that port exists, it is (in my limited experience with ancient code) safe to assume that the binary will run.
So how would this help you? Granted, this is a niche use case, but there are situations where the one library you need is written in C or Rust, and you just want to be able to use that bit of functionality; maybe you just want to bootstrap your project (then in the future you will rewrite it), or maybe it’s just enough for your use case.
One example is JRuby’s team port of the Prism Ruby parser, where they successfully used Chicory as a fallback on platforms where the native binary may not be available.
There are many other examples in the Go space, because the wazero project is much more mature than Chicory, and the Go ecosystem has already employed it successfully in many cases: in fact the Go runtime has a surprising number of similarities when it comes to limitations with interfacing with native code. My favorite must be Xe Iaso’s “Carcinization of Go Programs”, because everyone should run a Rust library from a Go program to parse Mastodon’s toots. But there is also the large collection of wasilibs, native tools re-built on top of wazero (including things like several plugins for protoc).
Another great example is Nuno Cruces’ Go SQLite Wasm port, built on top of wazero, is competitive in many ways with other alternatives. Indeed, there is also an experimental SQLite port of SQLite to Chicory called sqlite-zero.
The GraalVM team has also shared on their brand-new GraalWasm landing page an example of embedding C code running as a Wasm binary.
And, of course, you can run Doom in Swing using both GraalWasm and Chicory.
Bringing Computations In-Core
Wasm has caught the attention of many as a compact alternative to containers.
Indeed, serverless workloads are an excellent candidate for Wasm: you get a cross-platform, efficient, executable binary representation that can be compiled into fast native code. And, of course, the grumpy Java developer in you might mumble something about application servers, but hopefully we have already addressed that Wasm is not just “exactly the same” as Java Bytecode. Companies such as Fermyon and Cosmonic are building software platforms of this kind. CDN companies such as Fastly or Cloudflare are putting Wasm-computations at the edge.
But besides serverless containers, there is another space where cloud deployments could be replaced by Wasm “functions”. This is when a service plays the role of a logic “extension” to another service.
In this sense, such a service, be it a sidecar, be it a Webhook (or whatever fancy words you kids call them these days) can be often seen as a glorified “plug-in”.
Shopify with their “functions” were among the first mainstream platforms to adopt Wasm to that end.
There are a few reasons why Wasm is a good candidate for plugin embedding. First of all, it was explicitly designed to work in concert with JavaScript, which, nowadays, is the “extension language” for excellence! It was designed with the necessities of the web platform in mind: namely, security and sandboxing; in fact, it cannot access any feature of the host language without being explicitly given access to them (through what are called “host functions”). And obviously being cross-platform, efficient and compact.
Dylibso believes that all software should be “squishy”: that is, malleable, adaptable, and extensible; so they developed Extism, an open-source framework to build your own “function-as-a-service” layer inside your application. Or, to put it more simply, to easily embed your own plug-in system. At the time of writing Extism supports 16 “host” runtime platforms and over 10 “guest” languages to write your plugin (and counting!). Notably, for the Go host, Extism uses wazero, and for the Java platform, we are finalizing our Chicory SDK (Java is already supported through a native extension though).
What makes Wasm interesting as a JDK extension language is that it supports multiple languages, and the guest can be preemptively terminated, preventing it from hogging the CPU. Moreover, user-provided Wasm code can be safely loaded and unloaded in a controlled environment, making it an excellent choice for hosting user-defined functions in a database.
The GraalVM team has also released a set of compelling use cases for GraalWasm, including integrations with Micronaut and Spring Boot!
More Complex Use Cases
I have recently written a few use cases for Extism, Chicory and XTP. Both the following examples support compilation into a native binary, while retaining dynamic code loading capabilities.
Kafka Data Transforms
I have recently written an article on how to plug Chicory, Extism and XTP inside a native Quarkus application to build a Kafka data transform service. In this article I am giving a detailed explanation of how to use Extism, the XTP codegen tooling, and the XTP service to manage a data transform pipeline. Sounds intriguing? Check it out
There is also a follow-up in the pipeline, where I’ll explore extending the Kafka broker to the same effect. Stay tuned!
Quarkus Wasm Extension Demo
In the Quarkus Wasm Extension demo, I have implemented a middleware-style HTTP filter that you would usually run in a proxy or an API gateway like Envoy or NGINX using some API like Proxy-Wasm. Through the Quarkus extension and Chicory, you can compile the executable to a native image and load and reload extensions dynamically.
Essentially, a proxy such as Envoy implements a chain of HTTP request filters, it intercepts your requests, and then passes them through the filter. However, this requires you to deploy a fleet of proxies as sidecars alongside your application. On the one hand, makes it easier to manage policies from a central control plane; on the other hand, it might raise operational costs, because now you have even more services to deal with! Instead, you can turn some of these policies into interceptors inside your Quarkus application!
By cutting on the middleman, you are turning a complex multi-container deployment into a single application deployment! Pair that with XTP, and then you also get the missing control plane for your plugin policies!
Native DYNAMIC Software Extensions
Did I just mention dynamic code loading with Native Image? Indeed, I just did. One of the original limitations for an executable built into a native image, was the infamous “closed world assumption”, essentially, you have to instruct the native image builder about all the classes you expect to need at run-time, especially if you load them dynamically through run-time code reflection.
Limitations with reflections are however nowadays largely solved, because a lot more tooling is available to automatically inspect your record and report it to the compiler; there are embedded JSON manifests as well as automatic detection mechanisms; besides frameworks like Quarkus provide also sophisticated code-generation mechanisms that extend GraalVM’s built-in routines.
There have been also ways to dynamically load code: you can link against native libraries or you can use the Espresso Truffle runtime.
Espresso is particularly interesting because it’s another example of a VM-in-a-VM: it’s literally an implementation of the Java VM Specification that runs on top of another JVM. While, at a very first glance, this could feel like an intellectual experiment (you could probably run an Espresso instance on top of another Espresso instance, and then… it’s Espresso all the way down), it makes a lot of sense if you build one inside a native image! Then you can have your fast-to-boot, tiny, efficient, self-contained executable, and yet be able to load Java code in a sandboxed environment! You can literally have your cake and eat it too.
Now, why would you want to use Wasm instead? Well, because, why limit yourself to JVM languages? A Wasm runtime will allow you to dynamically load any Wasm code from your users!
If you are interested in this use case, read the details in the Extism+XTP Kafka demo, and the Quarkus Wasm Extension Demo.
Conclusions
It’s been a wonderful year for Wasm in the Java space. I hope this article sparked your interest too!
If you want to learn more about Extism and XTP, you can join us on our Discord, where we host weekly office hours on Wednesdays: ask all your questions about Wasm, Extism, XTP.
If you are especially interested in Chicory, you can also join our Zulip; and if you are the odd Gopher reading a Java blog, you might want to join the #wazero channel too.
Finally, if Extism and XTP sparked your interest and you’d like to have some fun with extending Claude.ai (and other LLMs supporting the Model Context Protocol) you might want to check out our brand-new project: mcp.run.
Author: Edoardo Vacchi
After my PhD at University of Milan on programming language design and implementation, I worked for three years at UniCredit Bank’s R&D department.
Later, I have joined Red Hat where I worked on the Drools rule engine, the jBPM workflow engine and the Kogito cloud-native business automation platform.
I joined Tetrate to work on the wazero WebAssembly runtime for Go.
Now, at Dylibso I still contribute to and wazero, and I am now also working on Chicory Wasm runtime for the JVM, and other runtimes!
I sometimes write on my own personal blog.