
Привет, Хабр.
Недавно в нашей компании возникла потребность в небольшой утилите для Windows пользователей.
Вся логика была уже реализована на Java библиотеках, так что оставалось только UI оболочка. Для этого была выбрана JavaFX. Приложение получилось красивое и удобное. На следующем шаге мы решили сделать исполняемое приложение из нашего JavaFX application, и для этого мы использовали инструмент jpackage.
Давайте рассмотрим, как это работает. Чтобы создать исполняемый файл для проекта JavaFX, вы можете воспользоваться несколькими подходами. Один из самых распространенных способов — использование инструмента jpackage, который входит в состав JDK начиная с версии 14. Этот инструмент позволяет упаковать ваш JavaFX проект в исполняемый файл для различных операционных систем.
Сборка исполняемого файла через gradle
Создание исполняемого файла происходит через специальный плагин: 'org.beryx.jlink' . Для Java17 подходит версия 2.24.1 (а для Java21 версия 3.1.1).
Документация здесь
Процесс делится на несколько этапов:
Сборка самого проекта в jar файл.
Подготовка образа Java.
Создание исполняемого файла для конкретной операционной системы.
Создание дистрибутива — установочного файла для конкретной операционной системы.
Мы рассмотрим вариант для ОС Windows.
Для этого шага нам понадобится дополнительно установить WiX Toolset.
Его последнюю версию можно скачать тут и добавить в переменную среды PATH.
Шаги:
У нас есть JavaFx приложение, которое собирается и запускается.
1. Подключить плагин в build.gradle
id 'org.beryx.jlink' version '2.24.1'
2. Добавить task в build.gradle
jlink {
imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
launcher {
name = 'WinApplication'
}
jpackage() {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
imageOptions += ['--icon', 'icon.ico']
}
imageOptions += ['--vendor', 'MyCompany'
]
installerOptions += [
'--vendor', 'MyCompany',
'--app-version', version
]
}
}
jlink
— создаст для нас образ java со всеми нашими зависимостями.
launcher {
name = 'WinApplication' - Здесь указывается наименование нашего приложения, будет соответственно 'WinApplication.exe'.
}
Jpackage
— отвечает за создание самого исполняемого файла и установочного файла.
imageOptions += ['--icon', 'icon.ico']
— определяет иконку для приложения, если не указывать эту опцию, то будет иконка по умолчанию.

'--vendor', 'MyCompany'
— если мы хотим указать производителя.
'--app-version', version
— определяет версию (важно не использовать при указании версии «SNAPSHOT» (например, “1.0.0-SNAPSHOT” — будет ошибка.)
3. Запускаем создание образа для инсталлятора
gradlew jpackageImage
получаем сам исполняемый файл.

По сути, WinApplication.exe уже можно запускать.

4. Запускаем создание дистрибутива
gradlew jpackage

Создались два файла для установки:WinApplication-1.0.0.exe и WinApplication-1.0.0.msi
Пример. build.gradle
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.10'
id 'org.beryx.jlink' version '2.24.1'
}
group 'com.example'
version '1.0.0'
repositories {
mavenCentral()
}
ext {
junitVersion = '5.8.1'
}
sourceCompatibility = '17'
targetCompatibility = '17'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
application {
mainModule = 'com.example.winapplication'
mainClass = 'com.example.winapplication.WinApplication'
}
javafx {
version = '17.0.1'
modules = ['javafx.controls', 'javafx.fxml']
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
}
test {
useJUnitPlatform()
}
jlink {
imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip")
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
launcher {
name = 'WinApplication'
}
jpackage() {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
imageOptions += ['--icon', 'icon.ico']
}
imageOptions += ['--vendor', 'MyCompany'
]
installerOptions += [
'--vendor', 'MyCompany',
'--app-version', version
]
}
}
jlinkZip {
group = 'distribution'
}
Пример всего проекта можно посмотреть тут
P.S. Для Linux данный плагин работает без изменений.


linuxApplication — наше приложение
linuxapplication-1.0.7-1.x86_64.rpm и linuxapplication-1.0.7-1_amd64.deb — установочные пакеты.

Приложение запускается!
Комментарии (4)
SserjIrk
14.02.2025 09:11Если GUI на JavaFX почему не использовали компиляцию в натив через GraalVM? плагин gluonfx (есть mvn есть gradle версии) дают вроде элементарный способ: mvn gluonfx:build и на выходе получается полностью самодостаточный нативный исполняемый файл.
Вроде даже позволяет мобильные apk и что-то там для яблок собирать. Но это я не пробовал. А вот для win и linux собирает очень шустрые образы. Ресурсов потребляют заметно меньше чем то-же самое запущенное в виде jar-а.
EvgenVasilev Автор
14.02.2025 09:11Самое главное, что jpackage - это инструмент из "коробки" JDK и мы умеем с ним работать. Нам показалось, что создание нативного образа с помощью GraalVM требует значительных усилий по настройке. Может понадобиться явно указывать, какие классы и ресурсы должны быть включены в образ, что может быть трудоемким процессом.
Так же у нас использовались библиотеки, которые пока не полностью поддерживаются в GraalVM.
А образ, который мы в итоге собрали через плагин GluonFX, получился несколько больше по размеру чем у jpackage.
TastaBlud
14.02.2025 09:11Спасибо, затронули больную тему: сама собираю приложение (и микрофреймворк для него подобный брошенному TornadoFX) для javafx. И всё бы хорошо, но плагин javafx для gradle сломали в версии 1 и потому приходится пользоваться 0.14 (нельзя выставить зависимости api вместо implementation - для монолитного приложения это ничего, но для фреймворка это важно)
А сам jlink плагин badass работает на ура: собирает и запускаемое приложение и инсталлятор, как положено, со всеми зависимостями, параметрами командной строки и прочим.
У меня не было проблем и нареканий.
volodin_igor
Лайк