GraalVM Native Image
GraalVM Native Image compiles a Nucleus app ahead of time into a self-contained native binary, with reflection, resource, and JNI metadata generated for every Nucleus module.
GraalVM Native Image ahead-of-time compiles a Compose Desktop app into a single self-contained binary that runs without a JVM. The trade-off is the closed-world assumption: no dynamic class loading, no agents, and every reflective call declared at build time. Nucleus generates that metadata for you, so the same source that builds a JVM app also builds a native image.
When to use GraalVM Native Image
- Background services, menu-bar apps, and system-tray tools that stay idle but must remain responsive.
- Utilities and launchers where startup time matters.
- Store distribution (App Store, MSIX, Snap) and small self-contained installers that ship no separate JRE.
- Memory-constrained environments such as sandboxes and low-spec hardware.
Trade-offs
- No JIT. A native image runs ahead-of-time compiled code with no just-in-time optimization, so sustained CPU-heavy workloads run slower than on the JVM. For those, use the AOT cache instead.
- Closed world. No runtime
Class.forNamedriven by dynamic values, no custom classloaders, no scripting engines, no runtime bytecode generation. Libraries that rely on these — Spring, Groovy, ByteBuddy-based mocking, heavy JNA use — are not compatible; ship them with the AOT cache. - Per-platform builds. A native image must be compiled on each target OS, so producing binaries for macOS, Windows, and Linux requires a CI matrix.
Most idiomatic Kotlin libraries — Ktor, kotlinx.serialization, Coil, SQLite, Jewel, Compose, SLF4J — work without extra configuration.
Requirements
GraalVM toolchain
Nucleus downloads and caches GraalVM Community Edition automatically on the first native
build — you no longer install a toolchain yourself. A GRAALVM_HOME environment variable takes
precedence if you want to point at your own. See Provision the toolchain.
Community Edition by default, Oracle GraalVM on request
GraalVM CE is the default because it is licensed under GPLv2 with the Classpath Exception,
which places no restriction on shipping it inside a paid application. Switch to Oracle GraalVM
with toolchain { distribution = GraalvmDistribution.ORACLE } when you need its exclusive
optimizations — Profile-Guided Optimization, -O3, ML-inferred profiles and advanced
obfuscation. Oracle GraalVM is free for production use under the GraalVM Free Terms and
Conditions, but the GFTC forbids charging any fee associated with redistributing it, so review
the terms before shipping a paid app. The deprecated AWT backend still requires a local
BellSoft Liberica NIK install for its AWT/Swing native-image support.
Platform toolchains
| Platform | Required |
|---|---|
| macOS | Xcode Command Line Tools |
| Windows | MSVC (Visual Studio Build Tools) |
| Linux | GCC, patchelf, and xvfb for headless compilation |
How Nucleus builds the native image
Three modules cooperate to produce a native image from the same source as a JVM build:
nucleus.graalvm-runtime— the runtime support library. It provides SVM@TargetClasssubstitutions for the AWT internals that don't work as-is under native image (the Fontconfig and Win32 font managers, font creation, the splash screen, and the X11 toolkit WMClass), registers app resources, and exposesGraalVmInitializer.- The Nucleus Gradle plugin — generates the reachability metadata (reflection, resources, JNI) for your dependencies and wires the
packageGraalvmNativetask graph. nucleus.nucleus-application— callsGraalVmInitializer.initialize()for you and exposes the sameDecoratedWindow/NucleusWindowAPI as the JVM path, so one source tree produces both artifacts.
nucleus.application {
mainClass = "com.example.MainKt"
graalvm {
isEnabled = true
imageName = "my-app"
}
}nucleusApplication calls GraalVmInitializer.initialize() at startup. If you write your own main() without it, call GraalVmInitializer.initialize() yourself before any AWT or Compose code runs.
What's next
- Configuration — the full
graalvm { }DSL and recommended build arguments. - Automatic metadata — how reflection, resources, and JNI are resolved for you.
- Tasks and CI — Gradle tasks, output locations, and the GitHub Actions matrix.
Benchmarks
Measured cross-runtime benchmark results — one Geekbench-style suite run inside a real Compose desktop app across JVM JIT, GraalVM Native Image, SwiftUI, Tauri, and Flutter.
Configuration
Reference for the graalvm { } DSL block that configures GraalVM native-image builds, including toolchain, image name, build arguments, metadata repository, and per-OS settings.