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 ofmain().- Sets Metal L&F,
java.home,java.library.path, charset init, preloads fontmanager, applies Linux HiDPI. - Ships SVM
@TargetClasssubstitutions for AWT font-manager edges. - Ships a
native-image.propertiesthat auto-includes SVGs, fonts, Compose resources, JNI libs, andMETA-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
| Concern | Action |
|---|---|
| Swing L&F | Sets swing.defaultlaf to Metal to avoid native L&F modules native-image doesn't ship. |
java.home | Points to the executable directory so Skiko finds jawt. |
java.library.path | Sets execDir and execDir/bin so fontmanager / freetype / awt are discoverable. |
| Charset init | Forces an early Charset.defaultCharset() to dodge InternalError: platform encoding not initialized. |
| Fontmanager preload | Calls System.loadLibrary("fontmanager") early to avoid crashes in Font.createFont(). |
| Linux HiDPI | Detects 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— buffersFont.createFont(int, InputStream)to a temp file on Windows; works around streams without mark/reset.Win32FontManagerSubstitution— pure-Java reimpl ofWin32FontManager.getFontPath(), fixesInternalError: 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:
| Pattern | Covers |
|---|---|
.*\.(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
RuntimeClassInitializationannotations in the per-module metadata or by lazyobjectinitialisation.
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.
Cross-links
- Linux HiDPI — used internally by
GraalVmInitializer. - Configuration — the
graalvm { }DSL block. - Automatic metadata — what reachability metadata is shipped on top of this module.
Automatic metadata resolution
Five layers of reflection / resource / JNI metadata merged at build time, so packageGraalvmNative produces a working binary without you writing JSON.
Tasks & CI
Gradle tasks added by the Nucleus plugin, output locations, and a GitHub Actions matrix that caches the native-image build.