Nucleus
Performance & nativeGraalVM Native Image

Runtime bootstrap

graalvm-runtime — the one-line GraalVmInitializer.initialize() call that prepares the JVM for native-image, plus the SVM substitutions and resource patterns it ships.

nucleus.graalvm-runtime is the runtime support library for native-image builds. It handles the half-dozen JVM properties, font preloading, and SVM substitutions that native-image needs to get a Compose Desktop app to first frame. One call from main() and the rest is implicit.

TL;DR

  • GraalVmInitializer.initialize() as the first line of main().
  • Sets Metal L&F, java.home, java.library.path, charset init, preloads fontmanager, applies Linux HiDPI.
  • Ships SVM @TargetClass substitutions for AWT font-manager edges.
  • Ships a native-image.properties that auto-includes SVGs, fonts, Compose resources, JNI libs, and META-INF/services.

Install

dependencies {
    implementation("dev.nucleusframework:nucleus.graalvm-runtime:<version>")
}

If you use nucleus.nucleus-application, this is pulled in transitively and GraalVmInitializer.initialize() runs inside nucleusApplication { … } — you don't need to call it yourself.

Quickstart

import dev.nucleusframework.graalvm.GraalVmInitializer
import androidx.compose.ui.window.application

fun main() {
    GraalVmInitializer.initialize()

    application {
        Window(onCloseRequest = ::exitApplication, title = "MyApp") {
            App()
        }
    }
}

The initializer is safe to call on a regular JVM — most of its work is gated on org.graalvm.nativeimage.imagecode. The Linux HiDPI scale detection runs unconditionally (no-op on macOS/Windows).

What GraalVmInitializer.initialize() does

ConcernAction
Swing L&FSets swing.defaultlaf to Metal to avoid native L&F modules native-image doesn't ship.
java.homePoints to the executable directory so Skiko finds jawt.
java.library.pathSets execDir and execDir/bin so fontmanager / freetype / awt are discoverable.
Charset initForces an early Charset.defaultCharset() to dodge InternalError: platform encoding not initialized.
Fontmanager preloadCalls System.loadLibrary("fontmanager") early to avoid crashes in Font.createFont().
Linux HiDPIDetects and applies the native scale factor via linux-hidpi.

You can also branch on GraalVmInitializer.isNativeImage to skip code that only makes sense on the JVM.

SVM substitutions

The module ships @TargetClass substitutions that patch the AWT font managers under native-image:

  • FontCreateFontSubstitution — buffers Font.createFont(int, InputStream) to a temp file on Windows; works around streams without mark/reset.
  • Win32FontManagerSubstitution — pure-Java reimpl of Win32FontManager.getFontPath(), fixes InternalError: platform encoding not initialized.
  • FcFontManagerSubstitution — same fix on Linux's Fontconfig path.

They are picked up by the native-image compiler automatically — no DSL flag.

Automatic resource inclusion

A native-image.properties shipped with the JAR registers four resource globs at build time:

PatternCovers
.*\.(svg|ttf|otf)All SVG icons and fonts on the classpath — Jewel, IntelliJ Platform, Compose resources, your own
composeResources/.*All Compose Multiplatform resources loaded via Res.*
nucleus/native/.*All Nucleus JNI shared libraries
META-INF/services/.*All ServiceLoader descriptors (ktor engines, coil fetchers, SLF4J providers, …)

This is the reason "icons render as empty squares in native-image" doesn't happen on Nucleus.

Binary size tradeoff

The .*\.(svg|ttf|otf) glob is wide on purpose. If you ship the IntelliJ Platform icons library and care about every megabyte, override with a narrower resource-config.json of your own.

Build-time vs runtime initialization

native-image runs Java code at build time during image construction, then again at runtime once the binary starts. Static initializers are normally executed at build time unless explicitly deferred. Nucleus arranges its runtime modules so that:

  • Pure data (icon catalogs, sealed interface registries, JSON schemas) initializes at build time — zero startup cost.
  • Anything touching the OS (file system, OS APIs, native libraries) is deferred to runtime, either by RuntimeClassInitialization annotations in the per-module metadata or by lazy object initialisation.

If you have a custom static initializer that touches the OS, declare it under RuntimeClassInitialization in your nativeImageConfigBaseDir. The Oracle Reachability Repository covers the well-known offenders (SLF4J, Logback, …).

Decorated window

The decorated-window-jni module is the one to use under native-image — it does not depend on JetBrains Runtime. Use decorated-window-jbr only on JVM builds.