JavaFX Integration
JavaFX, the cross-platform UI toolkit for Java-based applications, can easily integrate with the Jpackage plugin.
Prerequisites
Section titled “Prerequisites”You should already have a JavaFX application as a Gradle build. This guide describes the recommended setup and changes to apply in conjunction with the Jpackage plugin.
Plugins
Section titled “Plugins”Let’s begin with adding the necessary plugins to the build. We’ll avoid relying on unnecessary dependencies and skip the JavaFX plugin. Everything it does can be achieved with a little bit of build logic.
// build.gradle.ktsplugins { application id("org.openjfx.javafxplugin") version "0.1.0" id("de.infolektuell.jpackage") version "x.y.z"}JavaFX version and platform classifier
Section titled “JavaFX version and platform classifier”JavaFX has platform-specific native components, so we have to add the correct dependencies for the current platform. It consists of multiple modules that can be configured as a list of strings in the JavaFX plugin’s DSL extension. But we manually add the dependencies, so the version and platform have to be defined as variables in the build script.
First, add the JavaFX version as a Gradle property.
// gradle.propertiesorg.openjfx.javafx.version=25.0.2We can benefit from Gradle’s configuration-cache-friendly value sources and load the version as an external source provider.
// build.gradle.ktsval javafxVersion = providers.gradleProperty("org.openjfx.javafx.version")To detect the current platform and use the right classifier for the Maven dependencies, we define a little function that also returns a value source provider. Such helper functions could go to the bottom of the build script file, if they are too distracting.
// build.gradle.ktsfun findMavenPlatformClassifier(): Provider<String> { // Avoid internal API and use JVM system properties return providers.systemProperty("os.name").zip(providers.systemProperty("os.arch")) { os, arch -> val osPart = if (os.contains("windows", true)) "windows" else if (os.contains("mac", true)) "mac" else "linux" if (arch.contains("aarch64", true)) "$osPart-aarch64" else osPart }}// build.gradle.ktsval javafxVersion = providers.gradleProperty("org.openjfx.javafx.version")val javafxClassifier = findMavenPlatformClassifier().get()Last, the required modules have to be added:
// build.gradle.ktsval javafxVersion = providers.gradleProperty("org.openjfx.javafx.version")val javafxClassifier = findMavenPlatformClassifier()val javafxModules = setOf("base", "graphics", "controls", "fxml")Adding dependencies
Section titled “Adding dependencies”Now, everything is ready for dependencies.
// build.gradle.ktsval javafxVersion = providers.gradleProperty("org.openjfx.javafx.version")val javafxClassifier = findMavenPlatformClassifier()val javafxModules = setOf("base", "graphics", "controls", "fxml")
dependencies { javafxModules.forEach { implementation("org.openjfx:javafx-${it}:${javafxVersion.get()}:${javafxClassifier.get()}") }}Testing
Section titled “Testing”Now build and run to check if everything works correctly:
./gradlew build./gradlew runBonus: Local installation
Section titled “Bonus: Local installation”Sometimes, e.g., for debugging, it can be appropriate to use a local JavaFX installation as dependency.
This is something depending on the local environment, so the local installation path shouldn’t be persisted in the build script.
To achieve this, we can set the path in the user-level gradle.properties file that is mostly located in the .gradle directory in the user’s home directory.
First, create a user-level properties file and add a property with an installation path: See the gradle.properties docs for more information.
// ~/.gradle/gradle.propertiesorg.openjfx.javafx.local_installation=~/development/javafxLoad the path as a value provider as seen above and convert it to a directory provider:
// build.gradle.ktsval javafxInstallation = providers.gradleProperty("org.openjfx.javafx.local_installation") .map { layout.projectDirectory.dir(it) }Now we have to conditionally decide whether the local or Maven dependencies are added. So the dependencies block needs some decision logic. If the property is set, Gradle tries to load JavaFX from there and fails, if the path doesn’t exist or contains no jar files. Otherwise, it uses the Maven dependencies.
// build.gradle.ktsif (javafxInstallation.isPresent) { if (!javafxInstallation.get().asFile.exists()) throw GradleException("The local JavaFX installation directory ${javafxInstallation.get()} does not exist.") val javafxJars = fileTree(javafxInstallation).matching { include("*.jar") } if (javafxJars.isEmpty) throw GradleException("The local JavaFX installation directory ${javafxInstallation.get()} doesn't contain any JAR files.") repositories { flatDir { dirs(javafxInstallation.get()) } } dependencies { implementation(javafxJars) }} else { dependencies { javafxModules.forEach { implementation("org.openjfx:javafx-${it}:${javafxVersion.get()}:${javafxClassifier.get()}") } }}To return to the Maven dependencies, you have to remove or comment out the local installation property.
// ~/.gradle/gradle.properties// org.openjfx.javafx.local_installation=~/development/javafxWork done
Section titled “Work done”Now JavaFX should be available in your app. Try and test again:
# Development./gradlew build./gradlew run
# packaged app./gradlew appImage