Enkan supports two approaches to fast startup: GraalVM Native Image (compile to
a native binary) and CRaC (Checkpoint/Restore in Userspace). Both eliminate JVM
warm-up time; Native Image also reduces memory footprint, while CRaC can restore
a fully warmed-up JVM in milliseconds.
The kotowari-graalvm module provides two GraalVM
Feature
implementations that automate the reflection configuration required for a native build.
| Feature | Role |
|---|---|
EnkanFeature | Registers components, MixinUtils-generated classes, and SPI implementations |
KotowariFeature | Registers controllers, parameter types, return types, and generates a reflection-free dispatcher |
<dependency>
<groupId>net.unit8.enkan</groupId>
<artifactId>kotowari-graalvm</artifactId>
<version>0.15.0</version>
</dependency>
native-image.propertiesPlace the following in
src/main/resources/META-INF/native-image/<groupId>/<artifactId>/native-image.properties:
Args = --features=enkan.graalvm.EnkanFeature,kotowari.graalvm.KotowariFeature \
--initialize-at-build-time=enkan.util.MixinUtils,kotowari.graalvm.NativeDispatcherRegistry,kotowari.graalvm.KotowariDispatcher \
-H:+ReportExceptionStackTraces
The GenerateMixinConfig tool writes $Mixin class files and their
reflect-config.json entries at Maven prepare-package time, so they are
included in the fat JAR before native-image runs:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>generate-mixin-config</id>
<phase>prepare-package</phase>
<goals><goal>java</goal></goals>
<configuration>
<mainClass>kotowari.example.graalvm.GenerateMixinConfig</mainClass>
</configuration>
</execution>
</executions>
</plugin>
See kotowari-graalvm-example for a complete working example using Undertow + Jackson.
mvn -Pnative package
The resulting binary starts in single-digit milliseconds and uses a fraction of
the heap required by the JVM version.
Preview API note: If your application uses
RequestTimeoutMiddleware
(which relies onStructuredTaskScope, a preview API), add
--enable-previewto both thenative-imagebuild arguments and the
runtime invocation of the binary.
EnkanFeature:
- Registers all @Inject-annotated fields (including superclass hierarchy) for reflection
- Generates hidden-class ComponentBinder instances for reflection-free field injection
- Registers all META-INF/services SPI implementations
KotowariFeature:
- Calls MixinUtils.createFactory() at build time so all $Mixin subclasses exist in the image
- Registers all $Mixin classes for reflection
- Registers controller classes, their methods, and all parameter / return types
- Generates a KotowariDispatcher class that replaces reflective controller dispatch with
direct invokevirtual bytecode — zero reflection at runtime
CRaC takes a snapshot of a running JVM process (checkpoint) and restores it
later — potentially on a different host — with the JVM already warmed up. Enkan
integrates via the org.crac portability shim, so the same code runs on both
CRaC-capable JVMs and standard JVMs.
Call registerCrac() after system.start():
EnkanSystem system = EnkanSystem.of(
"app", new ApplicationComponent(AppFactory.class.getName()),
"http", new JettyComponent()
).relationships(
component("http").using("app")
);
system.start();
system.registerCrac(); // opt-in; no-op on non-CRaC JVMs
registerCrac() registers a Resource that:
- Calls system.stop() before the checkpoint is taken (closes sockets, flushes state)
- Calls system.start() after restore (re-opens sockets, reconnects to databases)
A second overload accepts an explicit Context<Resource> for testing:
system.registerCrac(Core.getGlobalContext()); // default
system.registerCrac(myTestContext); // for unit tests
With a CRaC-enabled JDK (e.g., Azul Zulu CRaC or OpenJDK CRaC builds):
# Start your application
java -XX:CRaCCheckpointTo=/tmp/checkpoint -jar myapp.jar
# In another terminal, trigger the checkpoint
jcmd <pid> JDK.checkpoint
java -XX:CRaCRestoreFrom=/tmp/checkpoint
The application is running immediately — no JVM startup, no JIT warm-up.
The org.crac portability shim absorbs registerCrac() silently. No exception
is thrown; the method is a no-op. The same binary runs on both CRaC and standard
JVMs.
| GraalVM Native Image | CRaC | |
|---|---|---|
| Startup time | Single-digit ms (cold) | Near-zero (warm restore) |
| Memory footprint | Significantly reduced | Same as JVM |
| JIT optimization | AOT (profile-guided optional) | Full JIT after restore |
| Build complexity | High (reflection config, native-image) | Low (standard JVM build) |
| Best for | Serverless, CLI tools, short-lived containers | Long-running services, scale-to-zero |