Native access — reflection metadata, resolved for you
Nucleus generates the reflection, resource, and JNI metadata a GraalVM Native Image build needs, merging it from several sources so the build compiles without hand-written JSON.
GraalVM Native Image uses a closed-world model: every reflective Class.forName, every
getResource pattern, and every JNI call has to be declared in JSON at build time. Nucleus
generates that metadata for you. It collects entries from five sources and hands them to
native-image, so ./gradlew packageGraalvmNative produces a working binary without a
hand-written reachability-metadata.json.
Add the dependency
The build tasks come from the Nucleus Gradle plugin. Add the runtime baseline so its bundled metadata, resource patterns, and AWT substitutions are on the classpath:
dependencies {
implementation("dev.nucleusframework:nucleus.graalvm-runtime:2.0.7")
}Build a native image
./gradlew packageGraalvmNativeThe task assembles metadata from every source below, passes each directory to native-image
via -H:ConfigurationFileDirectories=, and packages the resulting binary for the current OS.
How the metadata is assembled
Nucleus draws reachability metadata from five sources and merges them at build time:
| Source | What it covers | How it gets in |
|---|---|---|
| L1 — Per-library metadata | 28 curated files: Compose UI, Skiko, ktor, kotlinx.serialization, SQLite, Coil, JNA, FileKit, SLF4J, the JDK/AWT baseline, and more | Shipped in the plugin JAR. Files that declare matchPackages are included only when that library is on the runtime classpath. |
| L2 — Oracle Reachability Metadata Repository | SLF4J, Logback, and hundreds of other community libraries | Resolved from the official repository for each runtime dependency. |
| L3 — Platform metadata | sun.awt.windows.*, sun.lwawt.macosx.*, sun.awt.X11.*, Java2D pipelines, font managers, and security providers | Written to the build directory at compile time for the current target OS. |
| L4 — Static bytecode analysis | Class.forName, MethodHandles.Lookup.findClass, getResource[AsStream], JNI native methods with their field and parameter chains, ServiceLoader calls, and @Serializable types | The analyzeGraalvmStaticMetadata task scans every compiled class on the runtime classpath. |
| L5 — Runtime baseline | Resource-include patterns, AWT font-path substitutions, and serialization entries | Shipped inside nucleus.graalvm-runtime. Auto-detected from the JAR's META-INF/native-image directory. |
L1 through L4 and your own manual config are each passed as a -H:ConfigurationFileDirectories=
entry. The nucleus.graalvm-runtime baseline (L5) is picked up through GraalVM's standard
classpath detection. native-image merges all of them.
Included resources
The graalvm-runtime JAR registers four resource-include patterns:
| Pattern | Covers |
|---|---|
.*\.(svg|ttf|otf) | SVG icons and fonts on the classpath — Jewel, IntelliJ Platform icons, Compose resources, and your own |
composeResources/.* | Compose Multiplatform resources loaded through Res.* |
nucleus/.* | Nucleus JNI shared libraries and bundled runtime resources |
META-INF/services/.* | ServiceLoader descriptors (ktor engines, Coil fetchers, SLF4J providers, …) |
Binary size tradeoff
These patterns are broad on purpose. If you pull in the IntelliJ Platform icons library and
binary size matters, override the icon glob with a tighter resource-config.json of your own.
Font substitutions
The graalvm-runtime JAR ships @TargetClass substitutions that fix AWT font handling under
native image:
FontCreateFontSubstitution— buffersFont.createFont(int, InputStream)to a temp file before delegating, avoidingIOException: Problem reading font dataon Windows and Linux.Win32FontManagerSubstitution— replaces the nativesun.awt.Win32FontManager.getFontPathwith a pure-Java version, avoidingInternalError: platform encoding not initialized.FcFontManagerSubstitution— the same fix forsun.awt.FcFontManager.getFontPathon Linux.
The native-image compiler picks these up automatically; no configuration is needed.
Collect missing metadata with the tracing agent
Static analysis cannot see reflection driven by runtime values or classes loaded dynamically. For those cases, run the app once under the GraalVM native-image agent:
./gradlew runWithNativeAgentExercise every screen and feature. The agent records reflection, JNI, resource, and proxy accesses to a temp directory, then merges the new entries into your config without overwriting manually enriched ones.
Remove redundant manual metadata
If you have accumulated manual entries that the automatic sources now cover, prune them:
./gradlew cleanupGraalvmMetadataThe task compares your reachability-metadata.json against L1, L2, L3, and the static analysis
output, removes every entry already managed by Nucleus, and reports what it removed.
When automatic metadata isn't enough
Some libraries make GraalVM impractical regardless of metadata:
- Heavy JNA users (dynamic function-call proxies)
- Full-text search engines (Lucene, the Elasticsearch client)
- Embedded scripting runtimes (Groovy, JRuby, Nashorn)
- Runtime annotation processing (Spring)
- OSGi and custom classloaders
- Runtime bytecode generation (ByteBuddy, cglib, ASM-based mocking)
If you depend on any of these, ship the AOT cache build instead.
Write native code in Kotlin
To call a platform API that no Nucleus runtime module covers (CoreGraphics, IOKit, Win32,
GTK4, …), write that side in Kotlin/Native and let NucleusNativeAccess
generate the FFM bridge, the JVM proxies, and the GraalVM metadata for you. The
reflect-config.json, resource-config.json, and reachability-metadata.json it emits for the
generated proxies are picked up by the pipeline above transparently.
What's next
- Native code in Kotlin — call Kotlin/Native from the JVM through a generated FFM bridge.
- Automatic metadata — the full task graph behind these sources.
- GraalVM tasks and CI — the Gradle task reference for native builds.
- AOT cache — the fallback when a dependency rules out native image.
AOT cache
Generate a Project Leyden AOT cache at build time and bundle it with your installer to skip JVM warmup at launch.
Native code in Kotlin
Write the native side in Kotlin/Native and call it from the JVM as a plain Kotlin API. NucleusNativeAccess generates the FFM bridge, the JVM proxies, and the GraalVM metadata for you.