Nucleus
Performance & native

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:

build.gradle.kts
dependencies {
    implementation("dev.nucleusframework:nucleus.graalvm-runtime:2.0.7")
}

Build a native image

./gradlew packageGraalvmNative

The 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:

SourceWhat it coversHow it gets in
L1 — Per-library metadata28 curated files: Compose UI, Skiko, ktor, kotlinx.serialization, SQLite, Coil, JNA, FileKit, SLF4J, the JDK/AWT baseline, and moreShipped in the plugin JAR. Files that declare matchPackages are included only when that library is on the runtime classpath.
L2 — Oracle Reachability Metadata RepositorySLF4J, Logback, and hundreds of other community librariesResolved from the official repository for each runtime dependency.
L3 — Platform metadatasun.awt.windows.*, sun.lwawt.macosx.*, sun.awt.X11.*, Java2D pipelines, font managers, and security providersWritten to the build directory at compile time for the current target OS.
L4 — Static bytecode analysisClass.forName, MethodHandles.Lookup.findClass, getResource[AsStream], JNI native methods with their field and parameter chains, ServiceLoader calls, and @Serializable typesThe analyzeGraalvmStaticMetadata task scans every compiled class on the runtime classpath.
L5 — Runtime baselineResource-include patterns, AWT font-path substitutions, and serialization entriesShipped 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:

PatternCovers
.*\.(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 — buffers Font.createFont(int, InputStream) to a temp file before delegating, avoiding IOException: Problem reading font data on Windows and Linux.
  • Win32FontManagerSubstitution — replaces the native sun.awt.Win32FontManager.getFontPath with a pure-Java version, avoiding InternalError: platform encoding not initialized.
  • FcFontManagerSubstitution — the same fix for sun.awt.FcFontManager.getFontPath on 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 runWithNativeAgent

Exercise 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 cleanupGraalvmMetadata

The 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