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:
-
Automated setup with Claude - Quick and easy
-
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
Method 1: Automated Setup with Claude (Recommended)
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
-
Download and install the jDeploy Desktop App
-
Open jDeploy Desktop App

-
Click "Import Project…"

-
Select your Kotlin Compose Multiplatform project directory
-
Click "Import"
Step 2: Set Up with Claude
-
Open your project directory in your IDE or navigate to it in terminal
-
Run the
claudecommand in your project directory:claude -
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)
-
In the jDeploy Desktop App, make sure your project is selected
-
Click the "Publish" button
-
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
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:
-
Find or create a square PNG icon (256x256 or 512x512 recommended)
-
Name it
icon.png -
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
-
Download and install jDeploy desktop app from jdeploy.com
-
Open your project directory in jDeploy
-
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:
-
gradle/libs.versions.toml- Added Shadow plugin -
composeApp/build.gradle.kts- Added Shadow plugin, cross-platform deps, and build task -
package.json- Created with jDeploy configuration -
.github/workflows/jdeploy.yml- Created for automated builds
Key Changes Made:
-
Replaced
compose.desktop.currentOswith 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
-
Local Build Test: Run
./gradlew :composeApp:buildExecutableJar -
JAR Execution Test: Run
java -jar composeApp/build/libs/composeApp-all.jar -
Cross-Platform Verification: Check JAR contains all platform libraries
-
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.