Nucleus
Performance & nativeGraalVM Native Image

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.forName driven 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

BellSoft Liberica NIK 25

GraalVM native-image compilation requires the full BellSoft Liberica NIK 25 distribution. Download it yourself and install it locally — Gradle's automatic toolchain provisioning does not fetch the NIK variant.

Other distributions will fail

Oracle GraalVM, GraalVM CE, and Liberica NIK Lite lack the AWT/Swing native-image support that desktop apps need. Use the full Liberica NIK distribution.

Platform toolchains

PlatformRequired
macOSXcode Command Line Tools
WindowsMSVC (Visual Studio Build Tools)
LinuxGCC, 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 @TargetClass substitutions 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 exposes GraalVmInitializer.
  • The Nucleus Gradle plugin — generates the reachability metadata (reflection, resources, JNI) for your dependencies and wires the packageGraalvmNative task graph.
  • nucleus.nucleus-application — calls GraalVmInitializer.initialize() for you and exposes the same DecoratedWindow / NucleusWindow API as the JVM path, so one source tree produces both artifacts.
build.gradle.kts
nucleus.application {
    mainClass = "com.example.MainKt"
    graalvm {
        isEnabled = true
        imageName = "my-app"
        javaLanguageVersion = 25
    }
}

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.