Nucleus
Performance & nativeGraalVM Native Image

Tasks & CI

Gradle tasks added by the Nucleus plugin, output locations, and a GitHub Actions matrix that caches the native-image build.

The Nucleus Gradle plugin adds a small task graph for GraalVM, plus per-format packaging tasks (packageGraalvmDeb, …Dmg, …Nsis). Most of them are wired as dependencies of packageGraalvmNative and you never invoke them directly. This page is the reference.

Gradle tasks

TaskDescription
packageGraalvmNativeCompile and package the app as a native binary. The headline task.
runGraalvmNativeBuild and run the native binary directly.
runWithNativeAgentRun the app with the GraalVM tracing agent to collect reflection metadata.
analyzeGraalvmStaticMetadataScan compiled bytecode for reflection / JNI / resource patterns. Auto.
filterGraalvmLibraryMetadataFilter per-library L1 metadata based on the actual runtime classpath. Auto.
resolveGraalvmReachabilityMetadataResolve the Oracle Reachability Metadata Repository for classpath deps. Auto.
generateGraalvmPlatformMetadataGenerate platform-specific metadata for the current OS. Auto.
cleanupGraalvmMetadataRemove manual reachability-metadata.json entries that automatic metadata now covers.
packageGraalvmDebPackage the native image as a .deb installer (Linux).
packageGraalvmDmgPackage the native image as a .dmg installer (macOS).
packageGraalvmNsisPackage the native image as an NSIS .exe installer (Windows).

"Auto" tasks run as dependencies of packageGraalvmNative. The packaging tasks (packageGraalvm<Format>) require Node.js — they use electron-builder under the hood.

Common commands

# Raw binary, triggers every auto-metadata task
./gradlew packageGraalvmNative

# Build and run the binary
./gradlew runGraalvmNative

# Platform installers
./gradlew packageGraalvmDeb     # Linux
./gradlew packageGraalvmDmg     # macOS
./gradlew packageGraalvmNsis    # Windows

# Final safety net before release
./gradlew runWithNativeAgent

# Remove redundant manual metadata
./gradlew cleanupGraalvmMetadata

# Build for older CPUs
./gradlew packageGraalvmNative -PnativeMarch=compatibility

Output locations

<project>/build/compose/tmp/<project>/graalvm/output/
PlatformOutput
macOSoutput/MyApp.app/ — full .app bundle, Info.plist, icons, signed dylibs
Windowsoutput/my-app.exe + companion DLLs (awt.dll, fontmanager.dll, …)
Linuxoutput/my-app + companion .so files (libawt.so, libfontmanager.so, …)

Per-format installers land in:

<project>/build/compose/binaries/<buildType>/graalvm-<format>/

CI/CD — GitHub Actions

Native image must be compiled on each target OS. Use setup-nucleus with graalvm: 'true':

name: GraalVM release

on:
  push:
    tags: ["v*"]

jobs:
  build-natives:
    uses: ./.github/workflows/build-natives.yaml

  graalvm:
    needs: build-natives
    name: GraalVM - ${{ matrix.name }}
    runs-on: ${{ matrix.os }}
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        include:
          - { name: Linux x64,    os: ubuntu-latest }
          - { name: macOS ARM64,  os: macos-latest  }
          - { name: Windows x64,  os: windows-latest }

    steps:
      - uses: actions/checkout@v4

      # Download pre-built JNI native libs from the build-natives job here.

      - name: Setup Nucleus (GraalVM)
        uses: NucleusFramework/Nucleus/.github/actions/setup-nucleus@main
        with:
          graalvm: 'true'
          setup-gradle: 'true'
          setup-node: 'true'   # required for packageGraalvm<Format>

      - name: Build GraalVM native package
        shell: bash
        run: |
          case "$RUNNER_OS" in
            Linux)   ./gradlew :myapp:packageGraalvmDeb  -PnativeMarch=compatibility --no-daemon ;;
            macOS)   ./gradlew :myapp:packageGraalvmDmg  -PnativeMarch=compatibility --no-daemon ;;
            Windows) ./gradlew :myapp:packageGraalvmNsis -PnativeMarch=compatibility --no-daemon ;;
          esac

      - uses: actions/upload-artifact@v4
        with:
          name: graalvm-${{ runner.os }}
          path: myapp/build/compose/binaries/**/graalvm-*/**

setup-nucleus provisions Liberica NIK 25, the platform toolchain (Xcode CLT / MSVC / GCC + patchelf + xvfb), and primes Gradle's build cache. It is the single action that replaces a dozen ad-hoc setup steps. See CI/CD for the full release workflow with publishing to GitHub Releases.

Caching the native-image build

native-image compilation is the long step (5–15 minutes per platform). Use the standard Gradle cache via setup-nucleus plus a per-OS cache key for the .gradle/caches/<version>/native-image directory:

- uses: actions/cache@v4
  with:
    path: ~/.gradle/caches/nucleus/native-image
    key: nucleus-ni-${{ runner.os }}-${{ hashFiles('**/*.gradle.kts', '**/gradle.properties') }}
    restore-keys: |
      nucleus-ni-${{ runner.os }}-

This caches the resolved reachability metadata, the analysis output, and the metadata repository download.

Debugging

Missing reflection at runtime

Run the native binary from a terminal — reflection failures produce clear messages like Class not found or No such field. To fix:

  1. ./gradlew runWithNativeAgent, navigate the failing code path, let the agent capture the missing entry.
  2. Agent output is deduplicated — only truly new entries are added.
  3. Rebuild with ./gradlew packageGraalvmNative.

packageGraalvmDeb fails with electron-builder

The homepage property is required in nativeDistributions for DEB packaging — electron-builder refuses without it. See Configuration > Package metadata.

Best practices

  • Test on every platform early. Each OS has its own reflection footprint. Don't wait for release week.
  • Run the agent once before each release. Cheap, often finds nothing — and that's the assurance you want.
  • Reference the jewel-sample for the most complex production native-image setup we ship.

Further reading