Runtime Bootstrap¶
graalvm-runtime module¶
The graalvm-runtime module provides everything needed to bootstrap a Compose Desktop application in a GraalVM native image. Add it to your dependencies:
Then call GraalVmInitializer.initialize() as the first line of your main() function, before any AWT or Compose usage:
import io.github.kdroidfilter.nucleus.graalvm.GraalVmInitializer
fun main() {
GraalVmInitializer.initialize()
application {
Window(onCloseRequest = ::exitApplication, title = "MyApp") {
App()
}
}
}
The initializer handles all of the following automatically:
| Concern | What it does |
|---|---|
| Metal L&F | Sets swing.defaultlaf to avoid unsupported platform modules |
java.home |
Points to the executable directory so Skiko finds jawt |
java.library.path |
Sets execDir + execDir/bin so fontmanager/freetype/awt are discoverable |
| Charset init | Forces early Charset.defaultCharset() to prevent 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 (works in both JVM and native image) |
The native-image-specific steps only run when org.graalvm.nativeimage.imagecode is set. The Linux HiDPI detection runs unconditionally (it's a no-op on non-Linux platforms).
You can also check GraalVmInitializer.isNativeImage at any point to branch on native-image vs JVM execution.
Font substitutions¶
The module ships GraalVM @TargetClass substitutions (Java source files) that fix font-related crashes in native image on Windows and Linux:
FontCreateFontSubstitution— BuffersFont.createFont(int, InputStream)to a temp file on Windows, working around streams that lack mark/reset support in native image.Win32FontManagerSubstitution— ReplacesWin32FontManager.getFontPath()with a pure-Java implementation, fixingInternalError: platform encoding not initialized.FcFontManagerSubstitution— FixesFcFontManager.getFontPath()on Linux native image.
These substitutions are automatically picked up by the native-image compiler — no configuration needed.
Automatic Resource Inclusion¶
One of the most common pitfalls with GraalVM native-image is missing resources at runtime. Icons, fonts, and service descriptors must be explicitly registered — otherwise Class.getResource() returns null and your UI renders blank icons.
The graalvm-runtime module solves this automatically. It ships a native-image.properties file that registers broad resource patterns at compile time:
| Pattern | What it covers |
|---|---|
.*\.(svg\|ttf\|otf) |
All SVG icons and font files on the classpath — Jewel, IntelliJ Platform icons, Compose resources, your own icons |
composeResources/.* |
All Compose Multiplatform resources (images, strings, fonts loaded via Res.*) |
nucleus/native/.* |
All Nucleus JNI native libraries (.dll, .dylib, .so) |
META-INF/services/.* |
All ServiceLoader descriptors (ktor, coil, SLF4J, etc.) |
This means:
- All SVG icons work out of the box — Jewel's
PathIconKey,AllIconsKeys, dark/light variants,@2xretina variants — everything is included automatically. - All fonts are embedded — Inter, JetBrains Mono, or any custom
.ttf/.otfin your dependencies. - All Compose Multiplatform resources are included — images, strings, and other resources loaded via the
ResAPI. - Service loaders resolve correctly — ktor engines, coil fetchers, SLF4J providers, etc.
Binary size trade-off
The glob pattern .*\.(svg|ttf|otf) includes all SVGs and fonts from all JARs on the classpath. If you depend on the IntelliJ Platform icons library, this may add several megabytes of icons you don't actually use. For most applications, the convenience far outweighs the size increase. If binary size is critical, you can override with more targeted patterns in your own resource-config.json.
Decorated Window¶
The decorated-window-jni module was specifically designed to work with GraalVM Native Image (no JBR dependency). Use it instead of decorated-window-jbr for native-image builds.