Nucleus
Performance & nativeGraalVM Native Image

Configuration

Reference for the graalvm { } DSL block that configures GraalVM native-image builds, including toolchain, image name, build arguments, metadata repository, and per-OS settings.

The graalvm { } block inside nucleus.application { } configures GraalVM native-image builds: the toolchain, the output binary, the arguments passed to native-image, the reachability metadata, and the macOS and Windows settings. Every property is lazy and Property-typed. The block is defined on JvmApplication, so it sits alongside mainClass and nativeDistributions.

Enable a native image

Set isEnabled = true. Nucleus provisions the toolchain for you — a minimal configuration looks like this:

build.gradle.kts
nucleus.application {
    mainClass = "com.example.MainKt"

    graalvm {
        isEnabled = true
        imageName = "myapp"

        // Optional: optimize the image for size instead of the default -O2.
        optimization = NativeImageOptimization.SIZE

        metadataRepository {
            enabled = true           // default
            version = "1.1.4"        // default
            excludedModules.add("com.example:my-lib")
        }
    }
}

New in 2.1

The toolchain is downloaded automatically (see below) and defaults to GraalVM Community Edition, march and optimization are now type-safe enums, and Profile-Guided Optimization is built in. In 2.0 you installed a GraalVM toolchain yourself and passed size flags through buildArgs.

Provision the toolchain

By default Nucleus downloads and caches GraalVM Community Edition under <gradle-user-home>/nucleus/graalvm — no local install and no graalvm/setup-graalvm step needed. The download happens only when a native-image task actually runs: listing tasks or syncing the IDE never pulls a JDK. A GRAALVM_HOME environment variable takes precedence, provided it matches the requested distribution. The toolchain { } sub-block maps to GraalvmToolchainSettings.

build.gradle.kts
graalvm {
    toolchain {
        autoDownload = true                              // default
        distribution = GraalvmDistribution.COMMUNITY     // default
        channel = GraalvmChannel.INNOVATION              // default; LTS for the long-term-support line
        // version = "25"                                // optional; overrides channel
    }
}
PropertyTypeDefaultNotes
autoDownloadProperty<Boolean>trueSet false to resolve via Gradle's toolchain machinery (javaLanguageVersion / jvmVendor) instead.
distributionProperty<GraalvmDistribution>COMMUNITYCOMMUNITY (GraalVM CE) or ORACLE (Oracle GraalVM). See the licensing note below.
channelProperty<GraalvmChannel>INNOVATIONINNOVATION (latest release) or LTS. Used only when version is unset. Applies to both distributions.
versionProperty<String>unsetOverrides channel. Accepts "25i1", "25", or a pinned "25.0.1".
macosIntelFallbackProperty<Boolean>trueOn Intel Macs (dropped by both distributions after 25.0.1), fall back to BellSoft Liberica NIK.
installDirDirectoryProperty<gradle-user-home>/nucleus/graalvmCache location for the downloaded toolchain.

Choose a distribution

Oracle GraalVM changes your redistribution terms

GraalVM CE is licensed under GPLv2 with the Classpath Exception, which places no restriction on shipping it inside a paid application. Oracle GraalVM is governed by the GraalVM Free Terms and Conditions (GFTC), which permit production and commercial use but only allow redistributing the Program "provided that You do not charge Your licensees any fees associated with such distribution or use". Nucleus copies GraalVM runtime libraries (libjvm, libawt, …) next to the packaged executable, so this clause applies to your app bundle. Selecting ORACLE logs a build warning. Review the GFTC before shipping a paid application.

Opt into Oracle GraalVM when you need its exclusive optimizations — Profile-Guided Optimization, optimization = NativeImageOptimization.LEVEL_3 (-O3), ML-inferred profiles, and advancedObfuscation:

build.gradle.kts
graalvm {
    toolchain {
        distribution = GraalvmDistribution.ORACLE
    }
}

Under the default community toolchain those Oracle-only features degrade gracefully: -O3, --pgo and -H:AdvancedObfuscation are skipped with a warning instead of failing the build, and the runWithPgoInstrument task is not registered at all.

Install directories embed the distribution (graalvm-community-jdk-* vs graalvm-jdk-*), so switching never reuses the other build's download, and a GRAALVM_HOME whose distribution disagrees with the DSL is ignored with a warning.

To build on another community toolchain (Liberica NIK, Mandrel), point GRAALVM_HOME at it, or set autoDownload = false and pick a jvmVendor.

graalvm reference

PropertyTypeDefaultNotes
isEnabledProperty<Boolean>falseMaster switch for the native-image build.
imageNameProperty<String>package nameOutput binary name.
marchProperty<NativeImageMarch>per-platformNATIVE targets the build CPU; COMPATIBILITY targets older CPUs. Unset defaults to COMPATIBILITY, except Apple-Silicon macOS which defaults to NATIVE.
optimizationProperty<NativeImageOptimization>native-image -O2QUICK_BUILD (-Ob), NONE, LEVEL_1..LEVEL_3, SIZE (-Os). LEVEL_3 is Oracle GraalVM only.
allCharsetsProperty<Boolean>falsetrue emits -H:+AddAllCharsets (embed every JDK charset; only for legacy encodings).
mlProfileInferenceProperty<Boolean>truefalse emits -H:-MLProfileInference, opting out of Oracle's ML-inferred PGO.
advancedObfuscationProperty<Boolean>falsetrue emits -H:AdvancedObfuscation=, renaming symbols inside the binary. Oracle GraalVM only; ignored with a warning elsewhere.
buildArgsListProperty<String>emptyExtra arguments passed to native-image (win over the properties above).
javaLanguageVersionProperty<Int>25Toolchain language version — used only when toolchain { autoDownload = false }.
jvmVendorProperty<JvmVendorSpec>unsetToolchain vendor — used only when toolchain { autoDownload = false }.
nativeImageConfigBaseDirDirectoryPropertyDirectory of app-specific reachability-metadata.json. Rarely needed.
toolchainGraalvmToolchainSettingsauto-downloadToolchain provisioning (see above).
pgoGraalvmPgoSettingsenabledProfile-Guided Optimization (see below).
macOSGraalvmMacOSSettingsmacOS sub-block (see below).
windowsGraalvmWindowsSettingsWindows sub-block (see below).
metadataRepositoryMetadataRepositorySettingsenabledOracle Reachability Metadata Repository (see below).

Set build arguments

buildArgs are forwarded verbatim to native-image and win over the type-safe properties. Prefer optimization over a raw -Os/-O* and allCharsets over -H:+AddAllCharsets; reach for buildArgs for anything without a dedicated property:

ArgumentPurpose
-Djava.awt.headless=falseEnables GUI support, required for desktop apps.
-H:-IncludeMethodDataDrops method metadata, reducing binary size by several MB.

Automatic executable stripping

On Linux and macOS the main native executable is now stripped automatically, reclaiming tens of MB. Combine with optimization = NativeImageOptimization.SIZE for the smallest image.

Optimize with a PGO profile

Profile-Guided Optimization (Oracle GraalVM only) records how the app actually runs and feeds that profile back into the next build. It requires toolchain { distribution = GraalvmDistribution.ORACLE }; under the default community toolchain runWithPgoInstrument is not registered. The pgo { } sub-block maps to GraalvmPgoSettings.

build.gradle.kts
graalvm {
    pgo {
        enabled = true                                                    // default
        profile = layout.projectDirectory.file("graalvm/pgo/default.iprof") // default
    }
}
  1. Record: ./gradlew runWithPgoInstrument builds an instrumented image, runs it, and writes the profile to profile on exit. Exercise the hot paths before quitting.
  2. Commit the .iprof file. Later packageGraalvmNative / runGraalvmNative builds apply it automatically as --pgo=<profile>.

Disable a recorded profile for one build with -Pnucleus.graalvm.pgo=off. On community toolchains (GraalVM CE, Liberica NIK, Mandrel) --pgo is unavailable: a recorded profile is ignored with a warning, and the runWithPgoInstrument task does not exist.

PropertyTypeDefaultNotes
enabledProperty<Boolean>trueApply the profile automatically when the file exists.
profileRegularFilePropertygraalvm/pgo/default.iprofRecorded profile location.

Configure the metadata repository

The Nucleus plugin downloads the Oracle GraalVM Reachability Metadata Repository and resolves entries for every runtime dependency on the classpath. The metadataRepository { } sub-block maps to MetadataRepositorySettings.

PropertyTypeDefaultNotes
enabledProperty<Boolean>trueSet to false to skip the repository entirely.
versionProperty<String>"1.1.4"Repository artifact version.
excludedModulesSetProperty<String>emptygroup:artifact coordinates to skip.
moduleToConfigVersionMapProperty<String, String>emptyPins the metadata directory version for a given module.
build.gradle.kts
metadataRepository {
    moduleToConfigVersion.put("io.ktor:ktor-client-core", "3.0.0")
    excludedModules.add("group:noisy-lib")
}

Configure macOS settings

On macOS (arm64), native images on the default Tao backend build on the auto-provisioned toolchain (GraalVM CE by default). The deprecated AWT backend still requires BellSoft Liberica NIK. Intel Macs fall back to Liberica NIK.

The macOS { } sub-block maps to GraalvmMacOSSettings.

PropertyTypeDefaultNotes
cStubsSrcRegularFilePropertyFile of additional C stubs linked into the binary.
minimumSystemVersionProperty<String>"12.0"Patches the Mach-O LC_VERSION_MIN_MACOSX load command.
macOsSdkVersionProperty<String>"26.0"SDK version stamped into the launcher's Mach-O headers, which controls Liquid Glass eligibility.

Configure Windows settings

The windows { } sub-block maps to GraalvmWindowsSettings. GraalVM native images on Windows are dynamically linked against the Visual C++ runtime, which is not part of a clean Windows install. Bundling the runtime DLLs next to the executable lets the app start without the Visual C++ Redistributable.

PropertyTypeDefaultNotes
bundleCRuntimeProperty<Boolean>trueCopies the MSVC runtime DLLs next to the .exe.
dllsListProperty<String>vcruntime140.dll, vcruntime140_1.dll, msvcp140.dllDLL file names copied when bundleCRuntime is enabled.
sourceDirDirectoryPropertytoolchain binDirectory the DLLs are copied from. Point it at the MSVC redistributable if a DLL is missing from the toolchain.
build.gradle.kts
graalvm {
    windows {
        bundleCRuntime = true
        dlls.add("vcruntime140.dll")
    }
}

No release variant

Unlike the JVM build types, GraalVM has no release variant: there is no packageReleaseGraalvmNative and no runReleaseGraalvmNative. The native tasks are packageGraalvmNative and runGraalvmNative. This is intentional:

  • ProGuard's dead-code elimination is redundant. native-image already does closed-world dead-code elimination at compile time.
  • ProGuard can rename classes that are still referenced by reachability-metadata.json, which breaks the build silently.

Use optimization = NativeImageOptimization.SIZE for size optimization instead of ProGuard.

nativeImageConfigBaseDir is usually empty

Nucleus ships all generic and platform-specific metadata automatically. You only need nativeImageConfigBaseDir for app-specific entries the automatic layers don't cover, which is rare. See Automatic metadata for the five layers.

What's next