Native distributions
Here, you'll learn about native distributions: how to create installers and packages for all of the supported systems, and how to run an application locally with the same settings as for distributions.
Read on for the details about the following topics:
Details on basic tasks such as running an application locally, and advanced tasks like minification and obfuscation.
How to include JDK modules and deal with
ClassNotFoundException
.How to specify distribution properties: package version, JDK version, output directory, launcher properties, and metadata.
How to manage resources using resources library, JVM resource loading, or adding files to packaged applications.
How to customize source sets using Gradle source set, Kotlin JVM target, or manually.
How to specify application icon for each OS.
Platform-specific options, such as email of the package maintainer on Linux and the app category for the Apple App Store on macOS.
macOS-specific configuration: signing, notarization, and
Info.plist
.
Gradle plugin
This guide is primarily focused on packaging Compose applications using the Compose Multiplatform Gradle plugin. The org.jetbrains.compose
plugin provides tasks for basic packaging, obfuscation, and macOS code signing.
The plugin simplifies the process of packaging applications into native distributions using jpackage
and running an application locally. Distributable applications are self-contained, installable binaries that include all the necessary Java runtime components, without requiring a JDK to be installed on the target system.
To minimize package size, the Gradle plugin uses the jlink tool that ensures bundling only the necessary Java modules in the distributable package. However, you still must configure the Gradle plugin to specify which modules you need. For more information, see the Configuring included JDK modules
section.
As an alternative, you can use Conveyor, an external tool not developed by JetBrains. Conveyor supports online updates, cross-building, and various other features but requires a license for non-open source projects. For more information, refer to the Conveyor documentation.
Basic tasks
The basic configurable unit in the plugin is an application
. The application
DSL method defines a shared configuration for a set of final binaries, which means it allows you to pack a collection of files, together with a JDK distribution, into a set of compressed binary installers in various formats.
The following formats are available for the supported operating systems:
macOS:
.dmg
(TargetFormat.Dmg
),.pkg
(TargetFormat.Pkg
)Windows:
.exe
(TargetFormat.Exe
),.msi
(TargetFormat.Msi
)Linux:
.deb
(TargetFormat.Deb
),.rpm
(TargetFormat.Rpm
)
Here is an example of a build.gradle.kts
file with a basic desktop configuration:
When you build a project, the plugin creates the following tasks:
Gradle task | Description |
---|---|
| Packages the application into the corresponding |
| Aggregates all package tasks for an application. It is a lifecycle task. |
| Creates a single jar file containing all dependencies for the current operating system. The task expects |
| Runs an application locally from the entry point specified in |
| Creates a final application image without creating an installer. |
| Runs a prepackaged application image. |
All available tasks are listed in the Gradle tool window. After you execute a task, Gradle generates output binaries in the ${project.buildDir}/compose/binaries
directory.
Including JDK modules
To reduce the distributable size, the Gradle plugin uses jlink that helps bundle only the necessary JDK modules.
For now, the Gradle plugin does not automatically determine necessary JDK Modules. While this will not cause compilation issues, failure to provide the necessary modules can lead to ClassNotFoundException
at runtime.
If you encounter a ClassNotFoundException
when running a packaged application or the runDistributable
task, you can include additional JDK modules using the modules
DSL method:
You can specify the required modules manually, or run suggestModules
. The suggestModules
task uses the jdeps static analysis tool to determine possible missing modules. Note that the tool's output might be incomplete or list unnecessary modules.
If the size of the distributable is not a crucial factor and can be ignored, you may opt to include all runtime modules by using the includeAllModules
DSL property.
Specifying distribution properties
Package version
Native distribution packages must have specific package versions. To specify the package version, you can use the following DSL properties, listed from the highest priority level to the lowest:
nativeDistributions.<os>.<packageFormat>PackageVersion
specifies a version for a single package format.nativeDistributions.<os>.packageVersion
specifies a version for a single target OS.nativeDistributions.packageVersion
specifies a version for all packages.
On macOS, you can also specify the build version using the following DSL properties, listed, again, from the highest priority level to the lowest:
nativeDistributions.macOS.<packageFormat>PackageBuildVersion
specifies a build version for a single package format.nativeDistributions.macOS.packageBuildVersion
specifies a build version for all macOS packages.
If you don't specify a build version, Gradle uses the package version instead. For more information about versioning on macOS, see the CFBundleShortVersionString
and CFBundleVersion
documentation.
Here is a template for specifying package versions in order of priority:
To define a package version, follow these rules:
File Type | Version format | Details |
---|---|---|
|
|
|
|
|
|
|
|
For more details, see Debian documentation. |
| Any format | Version must not contain the |
JDK version
The plugin uses jpackage
, which requires a JDK version not lower than JDK 17. When specifying a JDK version, ensure you meet at least one of the following requirements:
The
JAVA_HOME
environment variable points to the compatible JDK version.The
javaHome
property is set via the DSL:compose.desktop { application { javaHome = System.getenv("JDK_17") } }
Output directory
To use custom output directory for native distributions, configure the outputBaseDir
property as shown below:
Launcher properties
To tailor the application startup process, you can customize the following properties:
Property | Description |
---|---|
| The fully-qualified name of the class containing the |
| Arguments for the application's |
| Arguments for the application's JVM. |
Here's an example configuration:
Metadata
Within the nativeDistributions
DSL block, you can configure the following properties:
Property | Description | Default value |
---|---|---|
| The application's name. | The Gradle project's name |
| The application's version. | The Gradle project's version |
| The application's description. | None |
| The application's copyright information. | None |
| The application's vendor. | None |
| The application's license file. | None |
Here's an example configuration:
Managing resources
To package and load resources, you can use the Compose Multiplatform resources library, the JVM resource loading, or add files to packaged applications.
Resources library
The most straightforward way to set up the resources for your project is to use the resources library. With the resources library, you can access resources in common code across all supported platforms. See Multiplatform resources for details.
JVM resource loading
Compose Multiplatform for desktop operates on the JVM platform, meaning you can load resources from a .jar
file using the java.lang.Class
API. You can access a file in the src/main/resources
directory via Class::getResource
or Class::getResourceAsStream
.
Adding files to packaged application
There are scenarios where loading resources from .jar
files may be less practical, for example, when you have target-specific assets and need to include files only in a macOS package but not in a Windows one.
In these cases, you can configure the Gradle plugin to include additional resource files in the installation directory. Specify a root resource directory using the DSL as follows:
In the example above, the root resource directory is defined as <PROJECT_DIR>/resources
.
The Gradle plugin will include files from the resources subdirectories as follows:
Common resources: Files located in
<RESOURCES_ROOT_DIR>/common
will be included in all packages regardless of the target OS or architecture.OS-specific resources: Files located in
<RESOURCES_ROOT_DIR>/<OS_NAME>
will be included only in packages built for a specific operating system. Valid values for<OS_NAME>
are:windows
,macos
, andlinux
.OS and architecture-specific resources: Files located in
<RESOURCES_ROOT_DIR>/<OS_NAME>-<ARCH_NAME>
will be included only in packages built for a specific combination of operating system and CPU architecture. Valid values for<ARCH_NAME>
are:x64
andarm64
. For example, files in<RESOURCES_ROOT_DIR>/macos-arm64
will be included in packages intended for Apple Silicon Macs only.
You can access included resources using the compose.application.resources.dir
system property:
Custom source sets
You can rely on default configuration, if you use org.jetbrains.kotlin.jvm
or org.jetbrains.kotlin.multiplatform
plugins:
Configuration with
org.jetbrains.kotlin.jvm
includes content from themain
source set.Configuration with
org.jetbrains.kotlin.multiplatform
includes content from a single JVM target. If you define multiple JVM targets, the default configuration is disabled. In this case, you need to configure the plugin manually, or specify a single target (see below).
If the default configuration is ambiguous or insufficient, you can customize it in several ways:
- Using a Gradle source set:
- plugins { kotlin("jvm") id("org.jetbrains.compose") } val customSourceSet = sourceSets.create("customSourceSet") compose.desktop { application { from(customSourceSet) } }
- Using a Kotlin JVM target:
- plugins { kotlin("multiplatform") id("org.jetbrains.compose") } kotlin { jvm("customJvmTarget") {} } compose.desktop { application { from(kotlin.targets["customJvmTarget"]) } }
- Manually:
Use
disableDefaultConfiguration
to disable the default settings.Use
fromFiles
to specify files to include.Specify the
mainJar
file property to point to a.jar
file containing the main class.Use
dependsOn
to add task dependencies to all plugin tasks.
compose.desktop { application { disableDefaultConfiguration() fromFiles(project.fileTree("libs/") { include("**/*.jar") }) mainJar.set(project.file("main.jar")) dependsOn("mainJarTask") } }
Application icon
Make sure your app icon is available in the following OS-specific formats:
.icns
for macOS.ico
for Windows.png
for Linux
Platform-specific options
Platform-specific settings can be configured using the corresponding DSL blocks:
The following table describes all supported platform-specific options. It is not recommended to use undocumented properties.
Platform | Option | Description |
---|---|---|
All platforms |
| Specifies the path to a platform-specific icon for the application. For details, see the Application icon section. |
| Sets a platform-specific package version. For details, see the Package version section. | |
| Specifies the absolute or relative path to the default installation directory. On Windows, you can also use | |
Linux |
| Overrides the default application name. |
| Specifies the email of the package maintainer. | |
| Defines a menu group for the application. | |
| Sets a release value for the rpm package or a revision value for the deb package. | |
| Assigns a group value for the rpm package or a section value for the deb package. | |
| Indicates the type of license for the rpm package. | |
| Sets a deb-specific package version. For details, see the Package version section. | |
| Sets an rpm-specific package version. For details, see the Package version section. | |
macOS |
| Specifies a unique application identifier, which can contain only alphanumeric characters ( |
| The name of the application. | |
| The name of the application as displayed in the menu bar, the "About <App>" menu item, and in the dock. Default value is | |
| The minimum macOS version required to run the application. For details, see | |
| See the Signing and notarizing distributions for macOS tutorial. | |
| Specifies whether to build and sign the app for the Apple App Store. Requires at least JDK 17. | |
| The category of the app for the Apple App Store. When building for the App Store, default value is | |
| Specifies the path to the file containing entitlements used when signing. When you provide a custom file, make sure to add the entitlements required for Java applications. See sandbox.plist for the default file used when building for the App Store. Note that this default file may differ depending on your JDK version. If no file is specified, the plugin will use the default entitlements provided by | |
| Specifies the path to the file containing entitlements used when signing the JVM runtime. When you provide a custom file, make sure to add the entitlements required for Java applications. See sandbox.plist for the default file used when building for the App Store. Note that this default file may differ depending on your JDK version. If no file is specified, the plugin will use the default entitlements provided by | |
| Sets the DMG-specific package version. For details, see the Package version section. | |
| Sets the PKG-specific package version. For details, see the Package version section. | |
| Sets the package build version. For details, see the Package version section. | |
| Sets the DMG-specific package build version. For details, see the Package version section. | |
| Sets the PKG-specific package build version. For details, see the Package version section. | |
| See the | |
Windows |
| Adds a console launcher for the application. |
| Enables customizing the installation path during installation. | |
| Enables installing the application on a per-user basis. | |
| Adds the application to the specified Start menu group. | |
| Specifies a unique ID which enables users to update the application via the installer, when there is a version newer than an installed version. The value must remain constant for a single application. For details, see How To: Generate a GUID. | |
| Sets the MSI-specific package version. For details, see the Package version section. | |
| Sets the EXE-specific package version. For details, see the Package version section. |
macOS-specific configuration
Signing and notarization on macOS
Modern macOS versions do not permit users to execute unsigned applications downloaded from the internet. If you attempt to run such an application, you'll encounter the following error: "YourApp is damaged and can't be open. You should eject the disk image".
To learn how to sign and notarize your application, see our tutorial.
Information property list on macOS
While the DSL supports essential platform-specific customizations, there can still be cases beyond the provided capabilities. If you need to specify Info.plist
values that are not represented in the DSL, you can include a snippet of raw XML as a workaround. This XML will be appended to the application's Info.plist
.
Example: Deep linking
Define a custom URL scheme in the
build.gradle.kts
file:
Use the
java.awt.Desktop
class to set up a URI handler in thesrc/main/main.kt
file:
Execute the
runDistributable
task:./gradlew runDistributable
.
As a result, links like compose://foo/bar
can now be redirected from a browser to your application.
Minification and obfuscation
The Compose Multiplatform Gradle plugin includes built-in support for ProGuard. ProGuard ia an open-source tool for code minification and obfuscation.
For each default (without ProGuard) packaging task, the Gradle plugin provides a release task (with ProGuard):
Gradle task | Description |
---|---|
Default: Release: | Creates an application image with bundled JDK and resources. |
Default: Release: | Runs an application image with bundled JDK and resources. |
Default: Release: | Runs a non-packaged application |
Default: Release: | Packages an application image into a |
Default: Release: | Packages an application image into a format compatible with the current OS. |
Default: Release: | Packages an application image into an uber (fat) `.jar`. |
Default: Release: | Uploads a |
Default: Release: | Checks if notarization succeeded (macOS only). |
The default configuration enables some pre-defined ProGuard rules:
The application image is minified, meaning unused classes are removed.
compose.desktop.application.mainClass
is used as the entry point.Several
keep
rules are included to ensure the Compose runtime remains functional.
In most cases, you don't need any additional configurations to get a minified application. However, ProGuard might not track certain usages in bytecode, for example, when a class is used via reflection. If you encounter issues that occur only after ProGuard processing, you may need to add custom rules.
To specify a custom configuration file, use the DSL as follows:
For more information about ProGuard rules and configuration options, refer to the Guardsquare manual.
Obfuscation is disabled by default. To enable it, set the following property via the Gradle DSL:
ProGuard's optimizations are enabled by default. To disable them, set the following property via the Gradle DSL:
Producing an uber JAR is disabled by default, and ProGuard produces a corresponding .jar
file for every input .jar
. To enable it, set the following property via the Gradle DSL:
What's next?
Explore the tutorials about desktop components.