Quarkus Chicory - An extension to easily integrate Chicory in your Quarkus applications
Overview
Quarkus Chicory integrates the Chicory WebAssembly runtime into Quarkus applications, providing a seamless way to execute WebAssembly modules within your Java applications. The extension abstracts away the complexity of WASM module management, compilation strategies, and execution modes through a simple injectable API.
Core Features
WasmQuarkusContext - The Central API
The WasmQuarkusContext is the primary interface between your application and WebAssembly modules. It provides
environment-aware access to WASM modules through CDI injection:
@Inject
@Named("my-module")
WasmQuarkusContext wasmContext;
What it provides:
-
MachineFactory- The execution engine configured for your environment:-
In dev mode: configured for runtime compilation to enable hot reload
-
In production: optimized for build-time compilation
-
In native image: configured for native execution
-
-
WasmModule- The parsed WebAssembly module:-
Loaded from configuration (file path or classpath resource)
-
Cached and reused across requests
-
Automatically reloaded in dev mode when source changes
-
Environment-aware behavior:
The WasmQuarkusContext automatically adapts based on:
-
Quarkus execution mode (dev, test, production, native)
-
Module configuration (static vs dynamic)
-
Performance settings (interpreter, runtime compiler, build-time compiler)
This means you write your code once, and the extension optimizes execution for each environment:
@PostConstruct
public void init() {
// Same code works in dev, production, and native image
// Extension provides the right MachineFactory for each
Instance instance = Instance.builder(wasmContext.getWasmModule())
.withMachineFactory(wasmContext.getMachineFactory())
.build();
}
Build-Time Code Generation
The extension generates Java bytecode from your WebAssembly modules at build time:
-
Replaces Chicory Maven plugin - no separate plugin needed
-
Type-safe access to exported functions through generated classes
-
Optimized performance through build-time compilation
-
Better IDE integration with code completion and type checking
Generated code is automatically available in the build output.
Dependency Management
Automatically handles version alignment between Quarkus and Chicory’s ASM dependencies:
-
No dependency conflicts - transparent version management
-
Just add the extension - no manual dependency configuration
-
Automatic updates when upgrading Quarkus or the extension
Multi WASM Module Support
Configure and manage multiple WebAssembly modules, each with its own WasmQuarkusContext:
quarkus.chicory.modules.module-a.wasm-file=module-a.wasm
quarkus.chicory.modules.module-b.wasm-file=module-b.wasm
@Inject @Named("module-a") WasmQuarkusContext moduleA;
@Inject @Named("module-b") WasmQuarkusContext moduleB;
Each module is independently configured with its own MachineFactory and WasmModule.
Static and Dynamic Module Loading
Static Configuration
Modules configured through application.properties with WasmQuarkusContext provided as injectable beans:
quarkus.chicory.modules.my-module.name=com.example.MyModule
quarkus.chicory.modules.my-module.wasm-file=${project.basedir}/src/main/resources/my-module.wasm
Benefits:
- WasmQuarkusContext injected automatically
- MachineFactory optimized for build-time compilation
- Automatic live reload in dev mode
- Native image support
Intelligent Execution Mode Selection
The extension configures the MachineFactory based on environment:
Development Mode (quarkus:dev)
-
MachineFactory: Runtime compiler for hot reload
-
WasmModule: Reloaded automatically when files change
-
Live reload: Edit WASM → instant reload → test immediately
Note on Chicory Annotations
The quarkus-chicory extension does not currently expose Chicory’s annotation processing capabilities
(@HostModule/@WasmExport) to users. The extension focuses on:
-
Injectable
WasmQuarkusContextbeans -
Automatic code generation from WASM modules
-
Environment-optimized
MachineFactoryconfiguration
If you want to use Chicory’s @HostModule/@WasmExport annotations in a Quarkus application, you would need to
manually add the Chicory annotation processor dependencies to your project.
Examples
Basic Static Configuration
This example demonstrates how to configure and use a static WebAssembly module loaded at build time.
Configuration
quarkus.chicory.modules.operation-static.name=io.quarkiverse.chicory.it.OperationModule
# The Wasm module payload is configurable either as a file
##quarkus.chicory.modules.operation.wasm-file=src/main/resources/wasm/operation.wasm
# Or as a classpath resource, but file the file based configuration takes precedence
quarkus.chicory.modules.operation-static.wasm-resource=operation.wasm
# Quarkus Chicory will watch all configured Wasm modules, so no need to add the following property:
#quarkus.live-reload.watched-resources=wasm/operation.wasm
The operation-static identifier is the module name used for injection. The module is loaded as a classpath resource
via wasm-resource, and the name property configures code generation.
Java Implementation
The above configuration will let your application inject a WasmQuarkusContext bean, like this:
@Path("/chicory")
@ApplicationScoped
public class ChicoryResource {
@Inject
@Named("operation-static")
WasmQuarkusContext wasmQuarkusContext;
Instance instance;
@PostConstruct
public void init() {
instance = Instance.builder(wasmQuarkusContext.getWasmModule())
.withMachineFactory(wasmQuarkusContext.getMachineFactory())
.build();
}
@GET
public Response hello() {
var result = instance.export("operation").apply(41, 1);
return Response.ok("Hello chicory: " + result[0]).build();
}
}
Exported Functions - Calling WASM from Java
This example demonstrates how to call functions exported by your WebAssembly modules.
Configuration
Configure your WASM module in application.properties:
quarkus.chicory.modules.calculator.name=io.quarkiverse.chicory.it.CalculatorModule
quarkus.chicory.modules.calculator.wasm-file=${project.basedir}/src/main/resources/calculator.wasm
WASM Module Exports
Your WebAssembly module exports functions that can be called from Java:
;; WebAssembly Text Format (WAT)
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add)
(func (export "multiply") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.mul)
(func (export "sqrt") (param f64) (result f64)
local.get 0
f64.sqrt)
)
Java Implementation
@Path("/calculator")
@ApplicationScoped
public class CalculatorResource {
@Inject
@Named("calculator")
WasmQuarkusContext wasmQuarkusContext;
Instance instance;
// Cache exported functions for better performance
ExportFunction addFunction;
ExportFunction multiplyFunction;
ExportFunction sqrtFunction;
@PostConstruct
public void init() {
instance = Instance.builder(wasmQuarkusContext.getWasmModule())
.withMachineFactory(wasmQuarkusContext.getMachineFactory())
.build();
// Export functions by name
addFunction = instance.export("add");
multiplyFunction = instance.export("multiply");
sqrtFunction = instance.export("sqrt");
}
@GET
@Path("/add/{a}/{b}")
public Response add(@PathParam("a") int a, @PathParam("b") int b) {
// Call exported function with parameters
long[] result = addFunction.apply(a, b);
return Response.ok(result[0]).build();
}
@GET
@Path("/multiply/{a}/{b}")
public Response multiply(@PathParam("a") int a, @PathParam("b") int b) {
long[] result = multiplyFunction.apply(a, b);
return Response.ok(result[0]).build();
}
@GET
@Path("/sqrt/{n}")
public Response sqrt(@PathParam("n") double n) {
// Use the alternative exports() API
var result = instance.exports()
.function("sqrt")
.apply(Value.fromDouble(n));
return Response.ok(result[0].asDouble()).build();
}
}
Dynamic Module Loading
This example demonstrates how to load WebAssembly modules dynamically at runtime, useful for plugin systems, multi-tenant applications, or user-uploaded WASM modules.
Configuration
Configure a dynamic module without specifying the WASM payload in application.properties:
# Define the module but don't specify wasm-file or wasm-resource
quarkus.chicory.modules.operation-dynamic.name=io.quarkiverse.chicory.it.DynamicOperationModule
# The Wasm module payload will be provided at runtime
The key difference from static modules is the absence of wasm-file or wasm-resource properties. The extension
creates the WasmQuarkusContext bean, but the actual WASM module is loaded later.
Java Implementation
@Path("/chicory/dynamic")
@ApplicationScoped
public class ChicoryDynamicResource {
@Inject
@Named("operation-dynamic")
WasmQuarkusContext wasmQuarkusContext; // Injected but no WASM loaded yet
Instance instance;
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response upload(@RestForm("module") FileUpload wasmModule) throws IOException {
try (InputStream is = Files.newInputStream(wasmModule.uploadedFile())) {
// Parse the uploaded WASM file
WasmModule module = Parser.parse(is.readAllBytes());
// Create instance using the MachineFactory from WasmQuarkusContext
instance = Instance.builder(module)
.withMachineFactory(wasmQuarkusContext.getMachineFactory()) // Environment-optimized
.build();
return Response.ok("Module loaded successfully").build();
}
}
@GET
public Response execute() {
if (instance == null) {
return Response.status(405)
.entity("No module loaded. Upload one first via POST /upload")
.build();
}
// Call the exported function
var result = instance.export("operation").apply(41, 1);
return Response.ok("Result: " + result[0]).build();
}
}
Host Imports - Calling Java from WebAssembly
This example demonstrates how to provide Java functions that WebAssembly modules can call (host imports).
Configuration
Configure your WASM module in application.properties:
quarkus.chicory.modules.operation.name=io.quarkiverse.chicory.it.OperationModule
quarkus.chicory.modules.operation.wasm-resource=operation.wasm
Java Implementation
Inject the module and provide host functions that the WASM module can import:
@Path("/chicory")
@ApplicationScoped
public class ChicoryResourceWithImports {
@Inject
@Named("operation")
WasmQuarkusContext wasmQuarkusContext;
Instance instance;
@PostConstruct
public void init() {
WasmModule wasmModule = wasmQuarkusContext.getWasmModule();
// Define a host function that WASM can call
HostFunction hostLog = new HostFunction(
"env", // Module name
"host_log", // Function name
FunctionType.of(List.of(ValType.I32), List.of()), // Signature: (i32) -> ()
(inst, args) -> {
int num = (int) args[0];
System.out.println("Called from WASM: " + num);
return null;
}
);
// Build instance with host imports
instance = Instance.builder(wasmModule)
.withImportValues(ImportValues.builder()
.addFunction(hostLog)
.build())
.withMachineFactory(wasmQuarkusContext.getMachineFactory())
.build();
}
@GET
public Response hello() {
// Call WASM function, which will call back to our host_log function
var result = instance.exports().function("operation").apply(41, 1);
return Response.ok("Result: " + result[0]).build();
}
}
WASI Integration with Exported Functions
This example shows how to integrate WASI (WebAssembly System Interface) support for modules that need system access.
Configuration
Configure the module in application.properties:
quarkus.chicory.modules.qrcode.name=io.quarkiverse.chicory.it.QRCodeModule
quarkus.chicory.modules.qrcode.wasm-file=${project.basedir}/src/main/resources/qr-generator.wasm
Java Implementation
Integrate WASI with the Quarkus-provided MachineFactory:
@Path("/qrcode")
@ApplicationScoped
public class QRCodeResource {
@Inject
@Named("qrcode")
WasmQuarkusContext wasmQuarkusContext;
Instance instance;
ExportFunction malloc;
ExportFunction free;
ExportFunction generateQR;
Memory memory;
@PostConstruct
public void init() throws IOException {
WasmModule wasmModule = wasmQuarkusContext.getWasmModule();
// Create WASI support
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
WasiOptions options = WasiOptions.builder()
.withStdout(stdout)
.withStderr(stderr)
.build();
WasiPreview1 wasi = WasiPreview1.builder()
.withOptions(options)
.build();
Store store = new Store().addFunction(wasi.toHostFunctions());
// Combine WASI with Quarkus MachineFactory
instance = Instance.builder(wasmModule)
.withMachineFactory(wasmQuarkusContext.getMachineFactory()) // Environment-optimized
.withImportValues(store.toImportValues()) // WASI functions
.withStart(false)
.build();
// Get exported functions
malloc = instance.export("malloc");
free = instance.export("free");
generateQR = instance.export("generateQR");
memory = instance.memory();
// Initialize WASM module (if needed)
try {
instance.export("_start").apply();
} catch (WasiExitException e) {
if (e.exitCode() != 0) {
throw new RuntimeException("WASM initialization failed");
}
}
}
@GET
public Response generate(@QueryParam("text") String text) {
byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
// Allocate memory in WASM
int textPtr = (int) malloc.apply(textBytes.length)[0];
try {
// Write data to WASM memory
memory.write(textPtr, textBytes);
// Call exported function
long[] result = generateQR.apply(textPtr, textBytes.length);
int qrPtr = (int) result[0];
// Read result from WASM memory
byte[] qrCode = memory.readBytes(qrPtr, result[1]);
return Response.ok(qrCode).build();
} finally {
free.apply(textPtr);
}
}
}
Memory Management - Passing Complex Data
This example demonstrates memory management patterns for passing complex data (strings, byte arrays) between Java and WebAssembly.
Configuration
quarkus.chicory.modules.go-cel.name=io.quarkiverse.chicory.it.GoCelModule
quarkus.chicory.modules.go-cel.wasm-file=${project.basedir}/src/main/resources/go-cel.wasm
WASM Module Requirements
The WASM module must export memory management functions:
-
malloc(size: i32) → i32- Allocate memory, returns pointer -
free(ptr: i32)- Free allocated memory -
evalPolicy(policyPtr: i32, policyLen: i32, inputPtr: i32, inputLen: i32) → i32- Process data using pointer/length pairs
Java Implementation
Use the exported malloc/free functions and memory access:
@Path("/policy")
@ApplicationScoped
public class PolicyResource {
@Inject
@Named("go-cel")
WasmQuarkusContext wasmQuarkusContext;
Instance instance;
ExportFunction malloc;
ExportFunction free;
ExportFunction evalPolicy;
Memory memory;
@PostConstruct
public void init() throws IOException {
WasmModule wasmModule = wasmQuarkusContext.getWasmModule();
// Configure WASI if needed
WasiPreview1 wasi = WasiPreview1.builder()
.withOptions(WasiOptions.builder().build())
.build();
Store store = new Store().addFunction(wasi.toHostFunctions());
instance = Instance.builder(wasmModule)
.withMachineFactory(wasmQuarkusContext.getMachineFactory())
.withImportValues(store.toImportValues())
.withStart(false)
.build();
// Export memory management functions
malloc = instance.export("malloc");
free = instance.export("free");
evalPolicy = instance.export("evalPolicy");
memory = instance.memory();
// Initialize Go runtime
try {
instance.export("_start").apply();
} catch (WasiExitException e) {
if (e.exitCode() != 0) throw new RuntimeException("Init failed");
}
}
@POST
public Response validate(String policy, String input) {
byte[] policyBytes = policy.getBytes(StandardCharsets.UTF_8);
byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
// Step 1: Allocate memory in WASM
int policyPtr = (int) malloc.apply(policyBytes.length)[0];
int inputPtr = (int) malloc.apply(inputBytes.length)[0];
try {
// Step 2: Write data to WASM memory
memory.write(policyPtr, policyBytes);
memory.write(inputPtr, inputBytes);
// Step 3: Call WASM function with pointer/length pairs
long[] result = evalPolicy.apply(
policyPtr, policyBytes.length,
inputPtr, inputBytes.length
);
int returnCode = (int) result[0];
// Step 4: Interpret results
if (returnCode == 1) {
return Response.ok("Policy allows").build();
} else if (returnCode == 0) {
return Response.ok("Policy denies").build();
} else {
return Response.status(400).entity("Error: " + returnCode).build();
}
} finally {
// Step 5: Free allocated memory
free.apply(policyPtr);
free.apply(inputPtr);
}
}
}
Installation
If you want to use this extension, you need to add the io.quarkiverse:quarkus-chicory extension first to your build
file.
For instance, with Maven, add the following dependency to your POM file:
<dependency>
<groupId>io.quarkiverse.chicory</groupId>
<artifactId>quarkus-chicory</artifactId>
<version>0.0.1</version>
</dependency>
Extension Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
The Wasm module file to be used. If Environment variable: |
string |
|
The Wasm module to be used. If Environment variable: |
string |
|
The base name to be used for the generated API class. Environment variable: |
string |
required |
The execution mode for a configured Wasm module Environment variable: |
|
|
The action to take if the compiler needs to use the interpreter because a function is too big Environment variable: |
|
|
The indexes of functions that should be interpreted, separated by commas Environment variable: |
list of int |