jpackage от А до Я: как правильно упаковать Java‑приложение в десктопный дистрибутив

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Привет, Хабр!

Начну с небольшой предыстории. Я начинал свой путь в IT с базового курса «Разработка программного обеспечения» в одной из профильных академий. Этого фундамента хватило для уверенного старта, но реальные коммерческие задачи часто выходят за рамки учебных программ.

Одной из таких «болей» стала дистрибуция Java‑приложений конечному пользователю. Сколько раз мы слышали от клиентов: «А у меня не запускается, наверное, Java не та»? Чтобы раз и навсегда избавиться от необходимости писать в README фразу «сначала установите JRE», я решил глубоко погрузиться в тему нативной упаковки.

В этой статье я систематизировал весь свой опыт работы с jpackage: от создания портативных версий до сборки полноценных инсталляторов для Windows, Linux и macOS, а также разобрал подводные камни при работе с JavaFX и Spring Boot.

jpackage— это мощный инструмент, встроенный в JDK (начиная с версии 14, JEP 343/392), который предназначен для упаковки Java-приложений в нативные образы для конкретных платформ. Он автоматически создает нативный лаунчер (запускатор) и включает в себя урезанную версию JRE (созданную черезjlink`), чтобы пользователю не нужно было устанавливать Java.

Далее так как я не знал изначально что умеет jpackage я для себя выделил два приоритета:
1. Создание Portable (портативных) приложений
2. Создание установочных пакетов (Installers)
Далее я расскажу о каждом из приоритетов и не только чтобы у каждого получилось поделиться своим приложением на Java без последующих лишних вопросов.

ПРИОРИТЕТ 1: Создание Portable (портативных) приложений

Портативное приложение (app‑image) — это папка, которую можно скопировать на любой компьютер (флешку, другой ПК) и запустить без установки. Она содержит ваш код, нативный лаунчер и скрытую JRE.

  1. Для ОС Windows

  • Результат: Папка с .exe файлом и подпапкой runtime.

  • Команда:

jpackage --type app-image --name "MyApp" --input ./target --main-jar myapp.jar --main-class com.example.Main --dest ./dist
  • Как сделать по‑настоящему portable: Просто заархивируйте полученную папку в ZIP. Пользователь распаковывает её и запускает .exe.

  • Важно: Если вы хотите, чтобы приложение работало на старых версиях Windows (например, Win 7), убедитесь, что вы собираете его на JDK, скомпилированной для нужной архитектуры, и учитывайте зависимости C++ runtime (обычно jpackage подтягивает нужные DLL, но иногда требуется Visual C++ Redistributable).

<ol start=«2»>
<li>Для ОС Linux</li>
</ol>

  • Результат: Папка с исполняемым скриптом/бинарником и подпапкой runtime.

  • Команда:

jpackage --type app-image --name "MyApp" --input ./target --main-jar myapp.jar --main-class com.example.Main --dest ./dist
  • Ограничения «портативности» в Linux: В отличие от Windows, Linux‑портативность условна. Скомпилированная JRE будет зависеть от версии glibc (GNU C Library) и графических библиотек (X11/Wayland, GTK) той системы, где происходила сборка.

  • Решение:* Собирайте portable‑версию для Linux на самой старой целевой версии дистрибутива (например, на Debian 10 или Ubuntu 18.04), чтобы обеспечить обратную совместимость glibc.

ПРИОРИТЕТ 2: Создание установочных пакетов (Installers)

jpackage умеет создавать нативные инсталляторы. Внимание: для их создания на машине сборщика должны быть установлены сторонние утилиты.

  1. Windows (MSI и EXE)

  • Формат:** .msi (нативно через WiX Toolset).

  • Требования: Установлен WiX Toolset 3.0 или новее и добавлен в PATH.

  • Команда:

jpackage --type msi --name "MyApp" --app-version 1.0.0 --input ./target --main-jar myapp.jar --main-class com.example.Main --win-menu --win-dir-chooser --dest ./dist
  • Примечание: jpackage не создает .exe инсталляторы (вроде InnoSetup) напрямую. Он создает MSI. Если нужен EXE, MSI можно обернуть в EXE через WiX Burn Bundle или InnoSetup пост‑фактум.

<ol start=«2»>
<li>Linux (DEB и RPM) </li>
</ol>

  • Форматы: .deb (для Debian/Ubuntu), .rpm (для RHEL/Fedora).

  • Требования: Установлены dpkg-deb (для deb) и rpmbuild (для rpm).

  • Команда (для DEB):

jpackage --type deb --name "myapp" --app-version 1.0.0 --input ./target --main-jar myapp.jar --main-class com.example.Main --linux-shortcut --linux-menu-group "Utility" --dest ./dist

<ol start=«3»>
<li>macOS (DMG и PKG)</li>
</ol>

  • Форматы:** .dmg (образ диска), .pkg (инсталлятор).

  • Требования: Сборка должна происходить только на macOS.

  • Команда:

jpackage --type dmg --name "MyApp" --app-version 1.0.0 --input ./target --main-jar myapp.jar --main-class com.example.Main --mac-package-identifier com.example.myapp --dest ./dist

ПРИОРИТЕТ 3: Запуск разных типов Java‑приложений

Главное правило jpackage: он запускает то, что запускает стандартная java -jar, но через нативный лаунчер. Однако есть нюансы для разных UI.

  1. Консольные приложения (CLI)

  • Проблема: В Windows по умолчанию лаунчер jpackage скрывает консольное окно, так как считает приложение GUI‑ным.

  • Решение:** Используйте флаг --win-console.

  • Команда:

jpackage --type app-image --name "MyCLI" --input ./target --main-jar mycli.jar --main-class com.example.CLI --win-console --dest ./dist
  • Для Linux/macOS:* Консоль и так будет видна при запуске из терминала. Если нужен запуск по клику, создайте .sh скрипт‑обертку.

<ol start=«2»>
<li>Swing / AWT приложения</li>
</ol>

  • Особенности:** Работают «из коробки». Но в Windows могут быть проблемы с масштабированием (DPI) и рендерингом.

  • Решение: Передайте JVM‑аргументы для улучшения рендеринга.

  • Команда:

jpackage --type app-image --name "MySwingApp" --input ./target --main-jar myswing.jar --main-class com.example.SwingUI --java-options "-Dsun.java2d.d3d=true" --java-options "-Dsun.java2d.ddscale=true" --dest ./dist

<ol start=«3»>
<li>3. JavaFX приложения (Самый сложный случай!)</li>
</ol>

С версии JDK 11 JavaFX отделена от JDK. jpackage не умеет автоматически подтягивать JavaFX, если вы используете «fat jar» (JAR со всеми зависимостями внутри).

  • Вариант А (Модульное приложение — РЕКОМЕНДУЕТСЯ): Если ваше приложение модульное (есть module-info.java), jpackage сам всё поймет.

jpackage --type app-image --name "MyFXApp" --input ./target --main-jar myfx.jar --main-class com.example.FXApp --dest ./dist
  • Вариант Б (Немодульное приложение / Fat JAR): Вам нужно явно указать module-path и добавить модули JavaFX, иначе вы получите ошибку GraphicsDevice initialization failed.

jpackage --type app-image --name "MyFXApp" \
  --input ./target \
  --main-jar myfx.jar \
  --main-class com.example.FXApp \
  --module-path /path/to/javafx-sdk/lib \
  --add-modules javafx.controls,javafx.fxml \
  --dest ./dist

Лайфхак: Если не хотите возиться с --module-path, создайте класс‑пустышку, наследующий javafx.application.Application, и укажите его как --main-class.

<ol start=«4»>
<li>Spring Boot приложения (Ограничение!)</li>
</ol>

  • Проблема:** Spring Boot создает «fat jar» (JAR внутри JAR). jpackage НЕ ПОДДЕРЖИВАЕТ вложенные JAR‑файлы. Он не сможет найти ваш main-class.

  • Решение 1 (Правильное):** Используйте maven-shade-plugin или spring-boot-maven-plugin с конфигурацией layout=ZIP и PropertiesLauncher, чтобы распаковать зависимости.

  • Решение 2 (Обходное): Извлеките все зависимости из fat‑jar в папку lib перед запуском jpackage, и передайте все JAR‑ы в --input.

# Сначала распакуем Spring Boot fat jar
# Затем упакуем через jpackage, указав главный класс Spring Boot
jpackage --type app-image --name "MySpringApp" --input ./extracted-libs --main-jar my-spring-app.jar --main-class org.springframework.boot.loader.PropertiesLauncher --dest ./dist

ВСЁ, ЧТО МОЖНО И НЕЛЬЗЯ ДЕЛАТЬ С jpackage

ЧТО МОЖНО (Возможности)

  1. Бандлинг JRE:** Автоматически создает минимальную JRE (через jlink), уменьшая размер дистрибутива с ~300 МБ до ~30-50 МБ.

  2. Кастомизация JVM:** Передача любых аргументов виртуальной машине (--java-options), например, выделение памяти (-Xmx1024m), настройка кодировки (-Dfile.encoding=UTF-8).

  3. Иконки и ресурсы:** Назначение нативных иконок (.ico для Win, .icns для Mac, .png для Linux) через --icon.

  4. Использование готовой JRE: Если вы заранее собрали идеальную JRE через jlink, вы можете скормить её jpackage через флаг --runtime-image, чтобы он вообще не трогал модули.

  5. Добавление файлов: Копирование конфигов, README, баз данных в папку приложения через --resource-dir или --file-associations.

  6. Ассоциация файлов:** Привязка расширений файлов к вашему приложению (чтобы双击 по файлу .myext открывал его в вашей программе) через --file-associations.

  7. Подпись кода (Code Signing): jpackage позволяет подписывать приложение.

    Windows: Через --win-upgrade-uuid и пост‑обработку signtool.

    macOS: Через --mac-sign, --mac-signing-key-user-name, --mac-signing-keychain.

ЧТО НЕЛЬЗЯ (Ограничения)

  1. Создание ОДНОГО файла (.exe /.appimage):** jpackage никогда не создает один исполняемый файл. Он всегда создает папку (для app-image) или архив/инсталлятор, внутри которого лежит папка с JRE. Сделать «портативный exe на 50 МБ» штатными средствами нельзя (нужны сторонние упаковщики вроде Enigma Virtual Box, но они ломают JRE).

  2. Компиляция кода:** jpackage не компилирует .java в .class. Ему нужны готовые .jar или .class файлы.

  3. Кросс‑компиляция:** Вы не можете собрать .msi или .dmg на Linux. Вы не можете собрать .deb на Windows. Сборка инсталляторов возможна только на той ОС, для которой она предназначена. (Исключение: app-image для Linux можно собрать на Windows, но он не будет работать без WSL/Cygwin, так что на практике собирайте на целевой ОС).

  4. Нативное шифрование кода: jpackage не защищает ваш код от декомпиляции. Ваш .jar внутри пакета лежит в открытом виде. Для защиты используйте обфускаторы (ProGuard, Zelix KlassMaster) до упаковки.

  5. Поддержка Nested JARs:** Как указано выше, Spring Boot fat‑jar «из коробки» не поддерживается.

Источник: https://habr.com/ru/articles/1096612/


Интересные статьи

Интересные статьи

Существует немало бесплатных инструментов, способных выявлять веб-уязвимости, обнаруживать ошибки конфигурации и автоматизировать проверки безопасности. Если посмотреть на мировую IT-индустрию, огромн...
Всем привет! Меня зовут Дмитрий, я разработчик в Битрикс24. В этой статье хочу рассказать о том, как можно создавать кастомные действия в коробке и сравнить способы их создания. В официальной док...
При загрузке данных в Highload-блоки возможна ситуация, когда объем загружаемых данных очень велик.Тем не менее, самый распространенный путь для добавления данных - их перебор в цикле, и последующее д...
Подборка свежих новостей и материалов из мира PHP. В выпуске: PHP теперь использует GitHub для трекинга багов; вышел PhpStorm 2021.3; закончена поддержка PHP 7.3, а 7.4 — в режиме только секьюр...