Nucleus
Get started

Configuration

Configure the nucleus { } Gradle DSL — application metadata, packaging formats, per-OS options, GraalVM native image, AOT cache, deep links, file associations, and trusted CA injection.

The nucleus { } extension configures how your app is built and packaged, in your build.gradle.kts. It extends the Compose Desktop application DSL with a GraalVM native-image path, additional packaging formats, an AOT cache, deep-link protocols, and trusted CA injection.

Configure the application

Use nucleus.application { } for a JVM application. The nativeDistributions { } block holds all packaging configuration, and the graalvm { } block controls the native-image path.

build.gradle.kts
nucleus.application {
    mainClass = "com.example.MainKt"
    jvmArgs("-Xmx512m")

    nativeDistributions {
        packageName = "MyApp"
        packageVersion = "1.0.0"
        targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)

        modules("java.sql", "java.net.http")
        cleanupNativeLibs = true

        protocol("MyApp", "myapp")
        fileAssociation(
            mimeType = "application/x-myapp",
            extension = "myapp",
            description = "MyApp Document",
        )

        macOS { /* … */ }
        windows { /* … */ }
        linux { /* … */ }
    }

    graalvm {
        isEnabled.set(false)
    }
}

To configure a Kotlin/Native application instead, use nucleus.nativeApplication { }.

Choose a runtime strategy

Nucleus can ship your app as a bundled JVM, a GraalVM native image, or a JVM with an AOT cache.

build.gradle.kts
nucleus.application {
    mainClass = "com.example.MainKt"
    nativeDistributions {
        packageName = "MyApp"
        packageVersion = "1.0.0"
        targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
        cleanupNativeLibs = true
    }
}

Bundles a JRE alongside your app. This is the default and requires the least configuration.

build.gradle.kts
nucleus.application {
    mainClass = "com.example.MainKt"
    nativeDistributions {
        packageName = "MyApp"
        packageVersion = "1.0.0"
        targetFormats(TargetFormat.Dmg, TargetFormat.Nsis, TargetFormat.Deb)
    }
    graalvm {
        isEnabled.set(true)
        javaLanguageVersion.set(25)
        imageName.set("myapp")
        march.set("native")
        buildArgs.add("-O2")
    }
}

Compiles the app to a self-contained native binary. Reachability metadata for Nucleus modules is injected automatically.

build.gradle.kts
nucleus.application {
    mainClass = "com.example.MainKt"
    nativeDistributions {
        packageName = "MyApp"
        packageVersion = "1.0.0"
        targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
        enableAotCache = true
    }
}

Requires JDK 25 or later. The build generates an AOT cache and ships it with the launcher, so classes and profiles are pre-warmed at startup.

application { } reference

PropertyTypeDescription
mainClassString?Entry-point class (FQCN of the file containing fun main).
mainJarRegularFilePropertyOverride the main JAR; rarely needed.
javaHomeStringJDK used for packaging the JVM image.
argsMutableList<String>App arguments at launch. Also args(vararg String).
jvmArgsMutableList<String>JVM flags baked into the launcher. Also jvmArgs(vararg String).
nativeDistributions { }blockPackaging configuration.
buildTypes { release { proguard { } } }blockProGuard rules for release builds.
graalvm { }blockGraalVM native-image configuration.
disableDefaultConfiguration()fnOpt out of the auto-discovered source set.
from(SourceSet | KotlinTarget)fnPick a source set or KMP target explicitly.

nativeDistributions { } reference

Property / callDescription
targetFormats(...)Select packaging formats. See the format list below.
packageName, packageVersion, description, vendor, copyright, homepage, licenseFileApp metadata. homepage is required for Linux DEB.
modules(...), includeAllModulesJDK modules to include. Default: java.base, java.desktop, java.logging, java.net.http, jdk.accessibility, jdk.crypto.ec.
cleanupNativeLibsStrip non-target native libraries from dependency JARs.
enableAotCacheGenerate a JDK 25+ AOT cache.
splashImagePath (relative to app resources) to a splash PNG.
compressionLevelCompressionLevel.Store, Normal, or Maximum.
artifactNameTemplate. Default: ${name}-${version}-${os}-${arch}.${ext}.
trustedCertificates.from(...)Extra CA bundles imported into the bundled JDK cacerts.
protocol(name, vararg schemes)Register a custom URL protocol (deep link).
fileAssociation(mimeType, extension, description, linuxIconFile?, windowsIconFile?, macOSIconFile?)Register a file type. Call multiple times for multiple associations.
publish { }GitHub Releases, S3, or generic HTTP publishing config.
macOS { }, windows { }, linux { }Per-OS overrides.

Select packaging formats

targetFormats(...) takes any of the 18 TargetFormat values:

OSFormats
macOSDmg, Pkg
WindowsMsi, Exe, Nsis, NsisWeb, Portable, AppX
LinuxDeb, Rpm, AppImage, RawAppImage, Pacman, Snap, Flatpak
AnyZip, Tar, SevenZ
build.gradle.kts
nativeDistributions {
    targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
}

Pkg, AppX, and Flatpak are the store formats. See Packaging for per-format details.

Set per-OS options

Each macOS, windows, and linux block overrides metadata and behavior for that target.

build.gradle.kts
nativeDistributions {
    macOS {
        bundleID = "com.example.myapp"
        dockName = "MyApp"
        appCategory = "public.app-category.productivity"
        minimumSystemVersion = "12.0"
        iconFile.set(project.file("icons/app.icns"))

        infoPlist {
            extraKeysRawXml = """
                <key>NSCameraUsageDescription</key>
                <string>MyApp needs the camera for QR scanning.</string>
            """.trimIndent()
        }

        signing {
            sign.set(true)
            identity.set("Developer ID Application: My Company (TEAMID)")
        }
        notarization {
            appleID.set(System.getenv("APPLE_ID"))
            password.set(System.getenv("APPLE_APP_PASSWORD"))
            teamID.set("TEAMID")
        }

        // macOS SDK version linked into the launcher
        macOsSdkVersion = "26.0"
    }
    windows {
        packageName = "MyApp"
        upgradeUuid = "00000000-0000-0000-0000-000000000000"
        menu = true
        perUserInstall = true
        iconFile.set(project.file("icons/app.ico"))
        signing {
            // Authenticode signing config
        }
    }
    linux {
        packageName = "myapp"
        appCategory = "Utility"
        menuGroup = "Utility"
        debMaintainer = "ops@example.com"
        iconFile.set(project.file("icons/app.png"))
        rpmLicenseType = "MIT"
    }
}

See Code signing for the full signing and notarization surface.

build.gradle.kts
nativeDistributions {
    protocol("MyApp", "myapp")
}

This wires CFBundleURLTypes on macOS, registry entries on Windows, and MimeType=x-scheme-handler/myapp in the Linux .desktop file. Receive the URI at runtime through NucleusApplicationScope.onDeepLink { uri -> … }. See Deep links.

Associate file types

build.gradle.kts
nativeDistributions {
    fileAssociation(
        mimeType = "application/x-myapp",
        extension = "myapp",
        description = "MyApp Document",
        linuxIconFile = project.file("icons/file.png"),
        windowsIconFile = project.file("icons/file.ico"),
        macOSIconFile = project.file("icons/file.icns"),
    )
}

Call fileAssociation(...) once per file type.

Inject trusted CA certificates

build.gradle.kts
nativeDistributions {
    trustedCertificates.from(files("certs/company-proxy-ca.pem"))
}

Accepts PEM or DER. The certificates are imported into the bundled JDK's cacerts at build time, which is useful behind a corporate proxy. To trust the OS keychain at runtime instead, use native-ssl.

Configure a GraalVM native image

PropertyDefaultDescription
isEnabledfalseMaster switch.
javaLanguageVersion25GraalVM JDK to use.
jvmVendor(auto)Override the toolchain vendor.
imageName(auto)Output binary name.
march"native"Target CPU architecture.
buildArgs[]Extra native-image flags.
nativeImageConfigBaseDir(build dir)Where to write reflection and resource configs.
metadataRepository.enabledtrueUse the GraalVM Reachability Metadata Repository.
metadataRepository.version"0.10.6"Repository version.
metadataRepository.excludedModules[]Skip metadata for specific modules.
macOS.minimumSystemVersion"12.0"Minimum macOS target.
macOS.macOsSdkVersion"26.0"macOS SDK linked into the launcher.
windows.bundleCRuntimetrueBundle the MSVC runtime DLLs next to the binary.

See GraalVM for the full walkthrough.

Enable the AOT cache

build.gradle.kts
nativeDistributions {
    enableAotCache = true
}

Requires JDK 25 or later. The plugin runs a training pass that boots your app to the first frame, snapshots the JVM's class state, and ships the cache alongside the launcher. See AOT cache.

Shrink release builds with ProGuard

build.gradle.kts
buildTypes {
    release {
        proguard {
            isEnabled = true
            optimize = true
            obfuscate = false
            joinOutputJars = true
            configurationFiles.from(project.file("proguard-rules.pro"))
        }
    }
}

Release tasks are suffixed Release, for example packageReleaseDmg.

CompressionLevel.Maximum with AppImage causes very slow startup due to squashfs/FUSE decompression. Use Normal or Store for AppImage targets.

What's next