- Turn any Java program into a self-contained EXE
- Prerequisites
- Java 9+
- Maven
- NodeJS
- Step 1. Compile and Package your code into a jar.
- Step 2. Create a Java Runtime Environment
- Step 3. Bundle the Jar and the JRE into an executable
- Как в IntelliJ IDEA написать и собрать в исполняемый .exe файл приложение на JavaFX
- Процесс и трудности, с которыми пришлось столкнуться
- Вместо заключения
Turn any Java program into a self-contained EXE
Double-click to run is one of the easiest ways to open a program.
If the person you are sharing code with already has the right version of Java installed, they can double-click on a jar file to run it. You wrote it once, they can run it there.
If they don’t have Java installed, then there are ways to create a runnable installer like jpackage, but now they have to click through an installer to be able to run your code.
You can use Native Image to turn your code into an exe which won’t require them to have anything installed, but now you have to abide by the closed world assumption and that’s not always easy or possible.
So this post is going to focus on a fairly oonga boonga approach that will work for any app, regardless of what dependencies you include or JVM features you make use of.
The code along with an example GitHub workflow can be found in this repo and final executables can be found here.
Prerequisites
Java 9+
java --version jlink --version
Maven
NodeJS
Step 1. Compile and Package your code into a jar.
This toy program will create a basic window that has some text that you can toggle between being capitalized.
package example; import org.apache.commons.text.WordUtils; import javax.swing.*; import java.awt.*; public class Main public static void main(String[] args) var label = new JLabel("Hello, World!"); label.setFont(new Font("Serif", Font.PLAIN, 72)); var uppercaseButton = new JButton("Uppercase"); uppercaseButton.addActionListener(e -> label.setText(WordUtils.capitalize(label.getText())) ); var lowercaseButton = new JButton("lowercase"); lowercaseButton.addActionListener(e -> label.setText(WordUtils.uncapitalize(label.getText())) ); var panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.add(label); panel.add(uppercaseButton); panel.add(lowercaseButton); var frame = new JFrame("Basic Program"); frame.add(panel); frame.pack(); frame.setVisible(true); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); > >
The goal is to package up your code, along with its dependencies, into a jar. Jars are just zip files with a little extra structure.
For a Maven project the configuration will look like the following.
version="1.0" encoding="UTF-8"?> project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> modelVersion>4.0.0 example javaexe 1.0 UTF-8 18 18 org.apache.commons commons-text 1.9 org.apache.maven.plugins maven-shade-plugin 2.4.3 package shade implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> manifestEntries> example.Main 1.0
Where the «shade» plugin will handle including the code from all of your dependencies into the jar. In this case, the only external dependency is org.apache.commons/commons-text .
Then for the purposes of this guide we will move that jar into a new directory where it will be separate from whatever other files are in target/ .
mkdir build mv target/javaexe-1.0.jar build
Step 2. Create a Java Runtime Environment
In order to run the jar from the previous step, we will need to bundle it with a Java Runtime Environment. To do this we will use jlink .
Since the Java ecosystem hasn’t embraced modules, you most likely haven’t heard of or used jlink .
The short pitch is that it can create «custom runtime images.» Say you are making a web server. You don’t need AWT or Swing, so including all the code for that is a tad wasteful. With jlink you can make a JRE that doesn’t include the java.desktop module at all.
This system works best if your application and all of its dependencies include compiled module-info.java files which let jlink know exactly what modules you want to include. You can also manually figure out the list of required modules by using jdeps and a bit of detective work.
Even without a modular project though, we can still use jlink to effectively clone our Java installation to a directory.
jlink --add-modules ALL-MODULE-PATH --output build/runtime
Including every module gives confidence that libraries like org.apache.commons/commons-text will work as intended, even though we never figured out what modules they actually require.
Step 3. Bundle the Jar and the JRE into an executable
So with a jar containing our code and all of its dependencies in one hand and a JRE in the other, all that’s left is to stitch the two together.
The general technique for that is to
- Zip up the directory containing the JRE and your application jar.
- Attach a stub script to the top of that zip file which will extract the zip to a temporary directory and run the code.
There is a JavaScript library which does this called caxa. Its purpose is making NodeJS projects into executables, so it will also bundle whatever NodeJS installation is on the system. That step can luckily be skipped by passing the —no-include-node flag, so it will work just fine for this.
npx caxa \ --input build \ --output application \ --no-include-node \ -- ">/runtime/bin/java" "-jar" ">/javaexe-1.0.jar"
This will create an executable called » application .» If you are doing this for Windows you should specify » application.exe .» When the executable is run the > s in the command will be substituted for to the temporary directory where the zip file was expanded.
I am aware of jdeploy — and it does handle stuff that I didn’t cover or would be relatively hard with this scheme like code signing or automatic updates — but as far as I can tell it still requires that users run an installer.
On code signing, there is an open issue with caxa to figure out how to do that. I can make another post or update this one if an approach is figured out. I don’t quite understand the issue, so I don’t feel qualified to comment.
If any mildly ambitious reader wants to try their hand at making caxa in a different language so this process isn’t dependent on the JS ecosystem I encourage it.
As always, comments and corrections welcome.
Как в IntelliJ IDEA написать и собрать в исполняемый .exe файл приложение на JavaFX
Как я уже писала в своей предыдущей статье, недавно, после двухлетнего обучения (обещали 12 месяцев, но жизненные и геополитические обстоятельства внесли свои коррективы в сроки получения дополнительного образования), я стала java-разработчиком.
Завершение обучения требовало написания выпускного проекта, которым я, изрядно поразмыслив, решила «убить сразу несколько зайцев»:
- проект должен быть написан мной самостоятельно (без команды),
- быть законченным и полноценным с точки зрения «бэка и фронта»,
- функционал проекта должен нести какую-то информативную пользу,
- проект станет подарком на день рождения.
Вспомнив все, чему меня «пытались научить», я решила, что за 1,5 месяца, отведенных на выпускную работу (с учетом загруженности по основному месту своей занятости), я вполне самостоятельно справлюсь с написанием оконного приложения для рабочего стола по предоставлению пользователю текущих и прогнозных значений погоды. Оболочку приложения решила выполнить на JavaFX, а для получения данных о погоде использовать JSON.
Когда я выслала преподавателю техническое задание с характеристиками проекта, он, восседая на удобном кресле где-то в пригороде германского города Мюнхен, меня, великовозрастную студентку из Санкт-Петербурга, «обрадовал и окрылил» тем, что знания по JavaFX в реальной жизни мне не пригодятся, и я рискую впустую потратить время…
Ну что же, такая откровенность на завершающей стадии обучения, конечно, похвальна. Осознав, что целый курс, посвященный разработке приложения на JavaFX, получается, еще год назад был пустой тратой времени, я решила не отступаться и попробовать реализовать задуманное.
Процесс и трудности, с которыми пришлось столкнуться
Не особо вдумываясь в тонкости платформы JavaFX и ее представлении в выпусках Java-8, я решила писать приложение на JDK-1.8 в среде разработки IntelliJ IDEA Community Edition, используя maven.
Начиная создавать проект стандартным путем и пытаясь упростить себе жизнь, я выбрала настройки, как показаны на скриншоте:
и, нажав на кнопку «Next», получила неожиданное для себя сообщение:
Да-да, я не знала той тонкости, что JavaFX, представленный как часть выпуска Java-8, был удален из JDK и перенесен в отдельный модуль в Java-11.
Ну что же, начав все сначала, я выбрала JDK-11.0.2, и у меня чудесным образом создался проект с необходимыми классами и ресурсными файлами, со всеми нужными зависимостями и способами сборки, автоматически прописанными в pom.xml файле:
Чтобы проверить, действительно ли работает минимальный функционал будущего приложения, я запустила класс HelloApplication.java и получила результат, свидетельствующий, что все в порядке:
Далее была длительная полуторамесячная работа над проектом, в процессе которой:
- я внесла изменения в базовый код, сформированный IntelliJ IDEA автоматически,
- создала сцены с помощью JavaFX Scene Builder 2.0,
- установила получение информации по JSON,
- и прочее, прочее, прочее… пока в среде разработки все не начало запускаться, открываться и отображаться так, как мне бы хотелось.
С финальной версией проекта и демонстрацией его возможностей можно ознакомиться на GitHub.
Теоретически, проект готов, образовательная цель достигнута, его можно сдать как выпускную работу и выдохнуть.
Но, не забываем, программа должна была стать подарком на день рождения. У именинника не установлена Java, чтобы запустить jar-файл проекта и наслаждаться результатом работы приложения. Значит, нужно собрать проект в исполняемый .exe файл, который будет запускаться на любом устройстве с установленной системой Windows.
«Ерунда какая!» — подумала я, — «У меня же IntelliJ IDEA, она все сама соберет и подскажет, в случае чего».
Захожу в File -> Project Settings -> Artifacts -> JavaFx application -> From Module ‘имя модуля’
Добавляю к артефакту всю библиотеку зависимостей, что у меня есть в проекте (краем глаза вижу красные символы, которые должны бы вызвать тревогу и заставить меня призадуматься, но я так увлечена процессом, что не придаю им значения):
Заполняю сведения об основном классе, который запускает приложение (поле Application class):
В той же вкладке указываю значение «all» в поле Native bundle и применяю настройки:
После вхожу в режим Build -> Build Artifacts, выбираю только что созданный артефакт и… получаю сообщение, что используемая версия JDK не способна собрать мой проект в необходимый пакет:
Начитавшись всего, чего только возможно, во всемирной паутине, я решаюсь на эксперимент:
1. Заменила в pom.xml файле версию Java 11 на 8, обновилась:
2. В File -> Project Structure -> Project указала корректную версию используемой SDK:
3. Закомментировала (можно удалить) файл module-info.java, сформированный Java-11 автоматически при создании проекта:
4. Проверила, запускается ли приложение до сборки:
5. Описанными выше способами снова создала и собрала артефакт. Но! – опять получила ошибку:
6. После непродолжительных поисков причины я поняла, что на ноутбуке, на котором написана моя программа, собрать ее в исполняемый .exe файл у меня пока не получится, т.к. в наименовании пути к директории не должно быть ни кириллицы, ни специальных символов:
И изменение имени учетной записи Windows вручную не приведет к изменению имени пользовательской папки.
7. Не рискнув «копаться в мозгах» установленной на ноутбуке операционной системы, я воспользовалась стационарным компьютером, не имеющим в наименовании директорий кириллицу, и спокойно собрала проект.
Запускаемый .exe файл располагается в папке out\artifacts\название артефакта\bundles\название проекта:
Вместо заключения
При кажущейся простоте создания приложения на JavaFX (особенно с помощью JavaFX Scene Builder 2.0 и бесплатных видео-уроков на различных ресурсах), написание функционала программы – это только половина пути. Программа должна работать, жить своей жизнью самостоятельно, без привязки к среде разработки. И, как показывает мой личный опыт, при сборке проекта и выпуске его в «большой мир» приходится преодолевать немало терний.
Надеюсь, что моя статья поможет кому-нибудь избежать ошибок, найти ответы на вопросы или просто сразу пойти правильным путем (например, создавать обычный maven-проект на Java-8, вручную прописывая в pom.xml зависимости и способ сборки, и всегда указывать на латинице имена учетных записей, директорий в своей операционной системе).