This tutorial walks through the complete process of configuring a vanilla Kotlin Compose Multiplatform project to work with jDeploy, enabling you to distribute your desktop application as native installers across Windows, macOS, and Linux.

You can see some example builds here.

We’ll cover two approaches:

  1. Automated setup with Claude - Quick and easy

  2. Manual setup - Step-by-step configuration details

Prerequisites

  • A Kotlin Compose Multiplatform project with a desktop target. (You can generate a basic project here.)

  • Gradle build system

  • Java 21 or higher installed

The fastest way to set up jDeploy is using Claude Code, which can automatically configure your entire project in minutes.

Step 1: Import Project to jDeploy

  1. Download and install the jDeploy Desktop App

  2. Open jDeploy Desktop App

    jdeploy main menu

  3. Click "Import Project…​"

    import dialog

  4. Select your Kotlin Compose Multiplatform project directory

    file chooser
  5. Click "Import"

Step 2: Set Up with Claude

  1. Open your project directory in your IDE or navigate to it in terminal

  2. Run the claude command in your project directory:

    claude
  3. Ask Claude to configure jDeploy:

    Set this project up for jDeploy

That’s it! Claude will automatically:

  • Add the Shadow plugin to your build configuration

  • Configure cross-platform dependencies

  • Create the necessary build tasks

  • Set up package.json with proper jDeploy configuration

  • Create GitHub Actions workflow (if desired)

  • Handle all the complex Gradle configuration details

Step 3: Build and Test Your Application

After Claude completes the setup, test that everything works:

# Build the cross-platform JAR
./gradlew :composeApp:buildExecutableJar

# Test the JAR runs correctly
java -jar composeApp/build/libs/composeApp-all.jar

You should see a JAR file created (~70-90MB) that contains native libraries for all platforms.

Step 4: Publish Your Application

Now for the exciting part - creating native installers for all platforms!

Option A: Using jDeploy Desktop App (Easiest)

  1. In the jDeploy Desktop App, make sure your project is selected

  2. Click the "Publish" button

  3. jDeploy will:

    • Build your application automatically

    • Create native installers for Windows, macOS, and Linux

    • Upload them to GitHub releases (if connected) or provide download links

Option B: Using jDeploy CLI

# Install jDeploy CLI (if not already installed)
npm install -g jdeploy

# Publish your app
jdeploy publish

Option C: Automatic Publishing with GitHub Actions

If you push your code to GitHub, the workflow Claude created will automatically:

  • Build your application on every push/tag

  • Create native installers for all platforms

  • Upload them as GitHub release assets

The Result

github release page
Figure 1. If using GitHub releases, your release page will look like this:

See example download page for a simple app here: https://github.com/shannah/jdeploy-kmp-starter/releases/latest

After publishing, you’ll have:

  • Native installers that users can download and install like any other desktop app

  • Cross-platform support - works on Windows, macOS, and Linux

  • Automatic updates (if configured)

  • Professional distribution without complex build processes

Users can install your app with a simple double-click, no Java installation required!

Why Use Claude?

  • Automatic: No manual editing of configuration files

  • Error-free: Claude handles all the complex Gradle syntax and dependency management

  • Up-to-date: Always uses the latest best practices and plugin versions

  • Project-aware: Adapts configuration to your specific project structure

  • Fast: Complete setup in under a minute

  • Complete workflow: From setup to published app in minutes


Method 2: Manual Setup (For Learning/Understanding)

If you prefer to understand exactly what’s happening or need to customize the setup, here’s the complete manual process that Claude automates:

Step 1: Add Shadow Plugin to Create Cross-Platform JARs

The Shadow plugin creates "fat JARs" that include all dependencies. This is essential for jDeploy to create a single distributable package.

1.1 Add Shadow Plugin Version

Edit gradle/libs.versions.toml and add the Shadow plugin version:

[versions]
# ... existing versions ...
shadowPlugin = "8.3.5"

[plugins]
# ... existing plugins ...
shadowPlugin = { id = "com.gradleup.shadow", version.ref = "shadowPlugin" }

1.2 Apply Shadow Plugin to Desktop Module

Edit composeApp/build.gradle.kts and add the Shadow plugin:

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.composeMultiplatform)
    alias(libs.plugins.composeCompiler)
    alias(libs.plugins.composeHotReload)
    alias(libs.plugins.shadowPlugin)  // Add this line
}

Step 2: Configure Cross-Platform Dependencies

By default, Compose Desktop uses compose.desktop.currentOs which only includes libraries for your current platform. For jDeploy, we need libraries for ALL platforms.

2.1 Replace Platform-Specific with All Platforms

In composeApp/build.gradle.kts, update the JVM dependencies:

kotlin {
    jvm()

    sourceSets {
        // ... other source sets ...
        jvmMain.dependencies {
            // Replace compose.desktop.currentOs with all platforms:
            implementation(compose.desktop.linux_x64)
            implementation(compose.desktop.linux_arm64)
            implementation(compose.desktop.macos_x64)
            implementation(compose.desktop.macos_arm64)
            implementation(compose.desktop.windows_x64)
            // Note: Windows ARM64 not yet supported in Compose Multiplatform

            implementation(libs.kotlinx.coroutinesSwing)
        }
    }
}

Step 3: Create Shadow JAR Build Task

Add a custom Shadow JAR task and a convenience build task for jDeploy.

3.1 Add Shadow JAR Configuration

Add this to the end of composeApp/build.gradle.kts:

tasks.register<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJarExecutable") {
    archiveClassifier.set("all")
    from(kotlin.jvm().compilations.getByName("main").output)
    configurations = listOf(project.configurations.getByName("jvmRuntimeClasspath"))
    manifest {
        attributes["Main-Class"] = "org.example.project.MainKt"  // Update with your main class
    }
}

tasks.register("buildExecutableJar") {
    dependsOn("shadowJarExecutable")
    doLast {
        println("Built executable JAR: composeApp/build/libs/composeApp-all.jar")
    }
}

Step 4: Create package.json Configuration

Create a package.json file in your project root with jDeploy configuration:

{
  "bin": {"kotlinproject": "jdeploy-bundle/jdeploy.js"},
  "author": "",
  "description": "Kotlin Compose Desktop Application",
  "main": "index.js",
  "preferGlobal": true,
  "repository": "",
  "version": "1.0.0",
  "jdeploy": {
    "jdk": false,
    "javaVersion": "21",
    "javafx": false,
    "title": "Kotlin Project",
    "platformBundlesEnabled": true,
    "jar": "composeApp/build/libs/composeApp-all.jar",
    "buildCommand": [
      "./gradlew",
      ":composeApp:buildExecutableJar"
    ]
  },
  "dependencies": {
    "command-exists-promise": "^2.0.2",
    "node-fetch": "2.6.7",
    "tar": "^4.4.8",
    "yauzl": "^2.10.0",
    "shelljs": "^0.8.4"
  },
  "license": "ISC",
  "name": "kotlinproject",
  "files": ["jdeploy-bundle"],
  "scripts": {"test": "echo \"Error: no test specified\" && exit 1"}
}

Key Configuration Fields:

  • name: Your NPM package name (must be unique if publishing to NPM)

  • jdeploy.javaVersion: Match this to your project’s Java version

  • jdeploy.jar: Path to the Shadow JAR created by your build

  • jdeploy.title: Human-readable application name

  • jdeploy.platformBundlesEnabled: Creates optimized per-platform bundles

  • jdeploy.buildCommand: Command jDeploy runs to build your JAR

Platform-Specific Bundle Configuration (Optional)

If you plan to publish to NPM, you can configure separate packages for each platform to reduce download sizes. Platform-specific bundles are typically ~40MB vs ~100MB for the universal bundle:

{
  "jdeploy": {
    "packageMacX64": "my-app-mac-x64",
    "packageMacArm64": "my-app-mac-arm64",
    "packageLinuxX64": "my-app-linux-x64",
    "packageLinuxArm64": "my-app-linux-arm64",
    "packageWinX64": "my-app-win-x64"
  }
}

Each platform package must be a unique NPM package name.

Step 5: Add Application Icon (Optional)

jDeploy looks for an icon.png file in your project root:

  1. Find or create a square PNG icon (256x256 or 512x512 recommended)

  2. Name it icon.png

  3. Place it in your project root (same directory as package.json)

Step 6: Create GitHub Actions Workflow (Optional)

For automated builds and releases, create .github/workflows/jdeploy.yml:

name: jDeploy CI

on:
  push:
    branches: ['*', '!gh-pages']
    tags: ['*']

jobs:
  build:
    permissions:
      contents: write
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Make gradlew executable
        run: chmod +x ./gradlew
      - name: Build with Gradle
        uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
        with:
          arguments: :composeApp:buildExecutableJar
      - name: Build App Installer Bundles
        uses: shannah/jdeploy@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
      - name: Upload Build Artifacts for DMG Action
        if: ${{ vars.JDEPLOY_CREATE_DMG == 'true' }}
        uses: actions/upload-artifact@v4
        with:
          name: build-target
          path: ./composeApp/build

  create_and_upload_dmg:
    if: ${{ vars.JDEPLOY_CREATE_DMG == 'true' }}
    name: Create and upload DMG
    permissions:
      contents: write
    runs-on: macos-latest
    needs: build
    steps:
      - name: Set up Git
        run: |
          git config --global user.email "${{ github.actor }}@users.noreply.github.com"
          git config --global user.name "${{ github.actor }}"
      - uses: actions/checkout@v3
      - name: Download Build Artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-target
          path: ./composeApp/build
      - name: Create DMG and Upload to Release
        uses: shannah/jdeploy-action-dmg@main
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          developer_id: ${{ secrets.MAC_DEVELOPER_ID }}
          # Team ID and cert name only needed if it can't extract from the certifcate for some reason
          # developer_team_id: ${{ secrets.MAC_DEVELOPER_TEAM_ID }}
          # developer_certificate_name: ${{ secrets.MAC_DEVELOPER_CERTIFICATE_NAME }}
          developer_certificate_p12_base64: ${{ secrets.MAC_DEVELOPER_CERTIFICATE_P12_BASE64 }}
          developer_certificate_password: ${{ secrets.MAC_DEVELOPER_CERTIFICATE_PASSWORD }}
          notarization_password: ${{ secrets.MAC_NOTARIZATION_PASSWORD }}

Step 7: Build and Verify

7.1 Make gradlew Executable

chmod +x ./gradlew

7.2 Build the Cross-Platform JAR

./gradlew :composeApp:buildExecutableJar

This should create a JAR file at composeApp/build/libs/composeApp-all.jar (approximately 70-90MB due to all platform libraries).

7.3 Verify Cross-Platform Support

Check that the JAR contains native libraries for all platforms:

# Check for Linux libraries
jar -tf composeApp/build/libs/composeApp-all.jar | grep "libskiko-linux"

# Check for macOS libraries
jar -tf composeApp/build/libs/composeApp-all.jar | grep "libskiko-macos"

# Check for Windows libraries
jar -tf composeApp/build/libs/composeApp-all.jar | grep "skiko-windows"

7.4 Test the JAR

java -jar composeApp/build/libs/composeApp-all.jar

Step 8: Using jDeploy

Option 1: jDeploy Desktop App

  1. Download and install jDeploy desktop app from jdeploy.com

  2. Open your project directory in jDeploy

  3. Click "Publish" to create installers

Option 2: jDeploy CLI

# Install jDeploy CLI globally
npm install -g jdeploy

# Create installers
jdeploy publish

Option 3: GitHub Actions (Automatic)

Simply push your code to GitHub. The workflow will automatically:

  • Build your application

  • Create native installers for all platforms

  • Upload them as release artifacts

Common Issues and Solutions

Issue: JAR only works on build platform

Solution: Ensure you’re including all platform dependencies, not just compose.desktop.currentOs

Issue: Large JAR size (70-90MB)

Expected: This is normal when including native libraries for all platforms

Issue: Build fails with "task already exists" error

Solution: Remove the application plugin if you have it - Compose Desktop already provides necessary tasks

Issue: Native access warnings when running JAR

Normal: Compose Desktop needs native access for rendering. These warnings are expected.

Complete File Changes Summary

Files Modified:

  1. gradle/libs.versions.toml - Added Shadow plugin

  2. composeApp/build.gradle.kts - Added Shadow plugin, cross-platform deps, and build task

  3. package.json - Created with jDeploy configuration

  4. .github/workflows/jdeploy.yml - Created for automated builds

Key Changes Made:

  • Replaced compose.desktop.currentOs with explicit platform dependencies

  • Added Shadow JAR task to create fat JAR with all dependencies

  • Configured jDeploy to use the Shadow JAR for distribution

Testing Your Setup

  1. Local Build Test: Run ./gradlew :composeApp:buildExecutableJar

  2. JAR Execution Test: Run java -jar composeApp/build/libs/composeApp-all.jar

  3. Cross-Platform Verification: Check JAR contains all platform libraries

  4. jDeploy Test: Use jDeploy app or CLI to create test installers

Conclusion

Your Kotlin Compose Multiplatform Desktop application is now fully configured for distribution with jDeploy! The setup enables you to:

  • Build once, run anywhere with a single JAR

  • Create native installers for Windows, macOS, and Linux

  • Automate releases with GitHub Actions

  • Distribute via NPM, GitHub Releases, or direct download

The cross-platform JAR ensures your application will run on any system with Java 21+, while jDeploy handles creating platform-specific installers for the best user experience.