Nucleus
Get started

Configuration

The full nucleus { } DSL — application metadata, native distributions, GraalVM, deep links, file associations, code signing and AOT cache.

All Nucleus build configuration lives inside nucleus { … } in your build.gradle.kts. The DSL extends JetBrains' compose.desktop.application with GraalVM native image, store-grade formats, AOT cache, deep-link protocols and trusted CA injection.

TL;DR

  • nucleus.application { … } for JVM apps; nucleus.nativeApplication { … } for Kotlin/Native (macOS only).
  • nativeDistributions { targetFormats(…) } selects packaging formats (16 supported).
  • graalvm { isEnabled.set(true) } flips to GraalVM Native Image.
  • enableAotCache = true enables the JDK 25 AOT cache for the JVM path.

Skeleton

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")
        enableAotCache = false
        cleanupNativeLibs = true

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

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

    graalvm {
        isEnabled.set(false) // flip to true to ship a native image
    }
}

Three common scenarios

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

Ships a bundled JRE alongside your app. Boots in ~1.5 s; smallest config surface.

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 to a self-contained binary (~38 MB, ~50 ms cold start). Reflection metadata for every Nucleus module is injected automatically.

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+. Generates an AOT cache during build; the shipped JVM boots in ~1 s with classes & profiles pre-warmed.

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.
jvmArgsMutableList<String>JVM flags baked into the launcher.
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 { … } — packaging

Property / callDescription
targetFormats(...)Pick formats: Dmg, Pkg, Nsis, Exe, Msi, AppX, Portable, Deb, Rpm, AppImage, Snap, Flatpak, Zip, Tar, SevenZ.
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 libs from dependency JARs.
enableAotCacheJDK 25+ AOT cache pre-warming.
splashImagePath (relative to app resources) to a splash PNG.
compressionLevelStore, Normal, Maximum. (Avoid Maximum with AppImage — squashfs cold-start cost.)
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 / generic HTTP publishing config.
macOS { … }, windows { … }, linux { … }Per-OS overrides.

Per-OS blocks (highlights)

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 26 Liquid Glass: vtool patches launcher Mach-O headers
        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"
    }
}
nativeDistributions {
    protocol("MyApp", "myapp")
}

Wires CFBundleURLTypes (macOS), registry entries (Windows), and MimeType=x-scheme-handler/myapp (Linux .desktop). Receive the URI at runtime through NucleusApplicationScope.onDeepLink { uri -> … } — see Deep links.

File associations

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 multiple times for multiple associations.

Trusted CA certificates

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

PEM or DER. Imported into the bundled JDK's cacerts at build time — useful for corporate proxies. For runtime trust against the OS keychain instead, use native-ssl.

GraalVM Native Image

PropertyDefaultDescription
isEnabledfalseMaster switch.
javaLanguageVersion25GraalVM JDK to use.
jvmVendor(auto)Override toolchain vendor.
imageName(auto)Output binary name.
march"native"Target CPU arch.
buildArgs[]Extra native-image flags.
nativeImageConfigBaseDir(build dir)Where to drop reflection / resource configs.
metadataRepository.enabledtrueUse the Oracle GraalVM Reachability Metadata Repo.
metadataRepository.version0.10.6Repo version.
metadataRepository.excludedModules[]Skip metadata for specific modules.
macOS.minimumSystemVersion"12.0"min macOS target.
macOS.macOsSdkVersion"26.0"macOS SDK linked into the launcher.

See GraalVM for the deep dive.

AOT cache (JVM path)

nativeDistributions {
    enableAotCache = true
}

Requires JDK 25+. The plugin runs a training pass that boots your app to first frame, snapshots the JVM's class state, and ships the cache alongside the launcher. At runtime, startup drops to roughly 1 s. See AOT cache.

ProGuard

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

Release tasks are suffixed Release (e.g. packageReleaseDmg).

AppImage + Maximum compression

Using CompressionLevel.Maximum with AppImage causes very slow startup (60s+) due to squashfs/FUSE decompression. Use Normal or Store for AppImage targets.

Next