Using the Java Packager with JDK 11
With the Java Packager and JDK 11 you can create a self-contained, installable bundle for your application for Mac, Linux or Windows.
Update: jpackage is now Production-Ready
This article is based on a very early, pre-release version of what has become jpackage. As of JDK 16, jpackage is now production-ready. For that reason, I’d recommend that you now use jpackage as described here in my new updated article, Installable Java Apps with jpackage, rather than using the technique described in this Medium article. However, I’ll leave this article here, just in case anyone still needs the reference.
Introduction
Would you like to package your Java 11 application so that anyone can install it and run it without first having to install Java? Would you like to distribute your work just like any other native application for Mac, Linux and Windows? I’ve done this for my Open Source project and in this article I’ll share how it was done, what problems I encountered and the solutions that I found. I hope that my experience will be a useful guide to anyone else attempting this in their own project.
Santulator: The Application Being Packaged
The application being packaged is Santulator, an Open Source program for running Secret Santa draws. All of the source code is available on GitHub and I encourage you to fork the repository and follow along. The application itself is entirely self-contained, has a JavaFX user interface and runs on Mac, Linux and Windows.
When someone is preparing a draw, Santulator offers the possibility to save the session, containing information about draw participants, out to a file. These session files have the extension .santa
and as part of this packaging work, these files need to be associated with the application. I think that this situation will be common to a lot of applications and most readers will be familiar with programs like Microsoft Word where double clicking a .doc
file will open that file in Word.
Beginning a Journey
Before we get into the detail, I should point out that this post represents the beginning of a journey. As you will see there are some problems to be ironed out in the packaging process and I’m sure lots of things that I will improve in my approach to it over time. It was important to me to release a first working version of Santulator and then to worry about perfecting the way that it is packaged in later releases. If you see any errors or things to be improved, please point them out in the comments. Even better, if you are able to improve something, do create a pull request and link that in your comment so that other people can clearly see how the problem is solved.
Where I point out problems here, this is no criticism of the hardworking people who maintain the various projects needed to package Santulator. And, of course, any mistakes you might find here are all my own. Santulator is very much a live project that will change and improve over time but since this document is a snapshot in time, I’ve fixed all the links to v1.1.0 of the software.
What Are We Trying to Achieve?
At this point, it’s worth spelling out exactly what I want to achieve. Here’s a list of the main aims:
- It should be simple for a non-technical user to install the packaged software.
- Once installed, the software should run just like any other program on the system.
- There should be installable bundles for Mac, Linux and Windows.
- The application should have a launch icon that then shows with the application when it is running, for example in the Dock on the Mac.
- When the packaged software is running, only the application windows should show. There should be no shell or batch file window left in the background.
- The installer should register the file extension
.santa
with the operating system. Session files (.santa
files) should then be shown in the operating system file browser with the associated session file icon. - Double-clicking a session file (
.santa
file) should open up the packaged application and show the saved session.
The Tools for the Job
To package up the software we need the right tools for the job. Here is a list of the main software I use in this example:
- AdoptOpenJDK provide pre-built JDK 11 binaries. These are a great choice for an Open Source project.
- Key to this is the Java Packager itself. As you can see from the link, this is actually a back-port of software that will be included in a later release of the JDK.
- JLink is used by the Java Packager to reduce the included parts of the JDK to just those modules that we actually need. Note that this doesn’t need to be dowloaded and installed separately as it comes with the JDK.
- Santulator has a JavaFX user interface so we need the JavaFX JMOD module files. These are available from Gluon.
- On Windows we need Inno Setup which is a tool for building installable bundles.
- It is convenient to include the creation of the installable bundle as part of the standard build process for the software. Santulator is built using Gradle so I have placed the packaging under the control of the Gradle build. Since Santulator uses the Gradle Wrapper, you don’t need to download and install Gradle separately.
Installing the Java Packager
As mentioned in the previous section, the Java Packager needs to be downloaded. For Mac and for Linux the software bundle can be uncompressed into any directory. We need to make a note of this directory as it has to be passed to the build process later for the packaging.
For Windows, the process is slightly different. When you unpack the Java Packager bundle you’ll find two files inside. I found that I needed to move these two files into the following directories inside the JDK itself:
jpackager.exe
➔%JAVA_HOME%\bin
jdk.packager.jar
➔%JAVA_HOME%\jmods
The Mystery of the Missing Windows DLLs
First I should confess that I’m no expert on Windows so bear with me on this. During my work on Santulator, I discovered a problem using the Java Packager on Windows. You can see the problem clearly in the following screenshots:
Windows warns that two vital files are missing: MSVCP140.dll
and VCRUNTIME140.dll
. After some experimentation, I’ve found that these files are needed both for the packaging process and for the final running program. Fortunately, the files are available as a download here on the Microsoft website. The Microsoft installer places the missing DLL files into C:\Windows\System32
. To ensure that these files are available to the packaged version of Santulator, the Gradle script copies them into the bundle directory before Inno Setup is run. You can see more details of this problem and the corresponding workaround in this issue on the Santulator repository.
Windows Inno Setup
To build the installable bundle on Windows, we need the Inno Setup software. This is a great tool for creating installers on Windows. In theory it should be possible to get the Java Packager to run Inno Setup directly, however this didn’t work for me. Instead on Windows I use the Java Packager to create a directory containing the required files that is then passed to Inno Setup. I also generate an .iss
file that Inno Setup reads, containing instructions to create the bundle.
Running the Java Packager
The output of the Java Packager depends on the operating system for which we are building the installable bundle. The Java Packager supports various different types of installers but here are the outputs I use for the three target operating systems:
- On the Mac I create a
.pkg
installer file. When launched, this guides the user through a wizard for installing the software on their Mac. - On Linux I create a
.deb
bundle. This can then be installed directly on Debian-based systems such as Ubuntu Linux. - On Windows I create a directory that is suitable for passing to Inno Setup for building the final installable bundle.
In the Santulator build, the Java Packager is launched from the Gradle script that in turn calls a Bash shell script on Linux and Mac and a batch file on Windows. These scripts simply run the Java Packager from the command line. To see how this is done in Santulator, take a look at build-package.sh for Linux and Mac and build-package.bat for Windows.
You can get a full list of the options available in the Java Packager by running jpackager
without any arguments.
JLink
As of Java 9, the JDK has now been modularised. This is great news for anyone building an installable bundle as it means that you need only include those parts of the JDK that your project requires. Amongst other things this helps to reduce the size of the bundle file created at the end of this process. If you’re interested in seeing an example of how JLink can be used, take a look at this article by Simon Ritter that provides a lot of useful detail.
When you run the Java Packager, you pass it a list of the Java modules that your project needs. The Java Packager then takes care of making the call to JLink to ensure that you have just the parts of Java required for your project. To make this work, you need to pass the Java Packager a list of modules. For Santulator you can see this list in the build-package.sh and build-package.bat files. I obtained this list using a combination of JDeps and a bit of trial and error and you’ll probably find the same works for you and for your project.
Wrapping It All up with Gradle
I chose Gradle as the build tool for Santulator. I decided to keep the shell and batch scripts simple and include the real logic in the Gradle script, build.gradle. I have found that it’s sometimes difficult to discover why the Java Packager has failed if, for example, a required file is missing. From this experience, my conclusion has been that the best way to reduce problems like these is to check as many prerequisites as possible in the Gradle script before launching the Java Packager. That way the Gradle script can fail with a useful error message rather than leaving the error handling to the Java Packager.
When the Gradle script runs, it needs two paths that are passed as Gradle properties on the command line by the user. These paths are then validated by Gradle and passed to the script that calls the Java Packager. These two options are:
- The path to the JavaFX JMOD files.
- The path to the Java Packager. Actually, you don’t need to pass this on Windows as you are required to install the Java Packager files directly inside directories under
JAVA_HOME
.
The following shows an example of launching the build on Linux, assuming that the JavaFX JMOD files are in /opt/javafx-jmods-11
and that the Java Packager itself is installed in /opt/jpackager-11
:
./gradlew clean createBundle \
-PjavafxJmodsPath=/opt/javafx-jmods-11 \
-PjavaPackagerPath=/opt/jpackager-11
Conceptually, calling the createBundle
Gradle task does the following, in order:
- Check all the prerequisites. This makes sure, for example, that the JavaFX modules path has been passed as a Gradle property and that it actually contains some module files.
- Copy the Java JAR file dependencies into place.
- Prepare the file association properties file. This file is later passed to the Java Packager with information about the
.santa
file extension for Santulator session files and the icon to associate with them. - Launch the Java Packager. This is done by running build-package.sh on Linux and Mac and build-package.bat on Windows.
- On Windows, copy the missing DLLs into place (see the section above describing this) along with the icon file for
.santa
session files. - On Windows, prepare the Inno Setup file.
Running Inno Setup on Windows
In theory, the Java Packager can run Inno Setup automatically and create the Windows setup file. However, I found that this didn’t work for me. Rather than delay the release of Santulator to look for a solution, I decided that it would be better just to use the Java Packager on Windows to create the image directory and then to launch Inno Setup manually. To make this easier, I make use of Gradle to prepare the .iss
file that contains the instructions for Inno Setup. You can see the code here in build.gradle that prepares the file.
The Gradle build script outputs on the console the location of the .iss
file. By double clicking this file, Inno Setup can be started in Windows and the installable bundle can be created.
Help, My Application Won’t Start!
If you’re anything like me, you won’t get this working the first time around. You’ll find you have an application that doesn’t start up and you’ll be left to debug the problem. So I thought I’d share a couple of tips I learned along the way to help.
Firstly, make sure you can run your application outside of your IDE. I achieved this in Gradle by applying the application
plugin in the build.gradle. Then you can check that everything builds and runs on JDK 11 from the command line, without the IDE, using the following:
./gradlew clean :gui:run
Doing this, you’ll probably discover that you need to pass some extra command-line parameters to Java to handle a few complexities relating to Java modules. You can see how this is done for Santulator here in build.gradle.
The second trick I discovered is to first get everything working with create-image
on the Java Packager before you move on to create-installer
. You can see the create-installer
command for Santulator here in build-package.sh. Aside from running faster, create-image
will leave you with a file that you can easily launch from the command line and thus see any launch errors that might appear on the console. If you try this with Santulator on the Mac, for example, you will find that the following file is created that can be run from the command line:
package/build/bundle/Santulator.app/Contents/MacOS/Santulator
The Finished Product
This really does work! Download Santulator to see an example and try it out yourself on your own project. Here’s the installer that I produced running on a Mac:
On Windows:
And on Linux:
Fork the Santulator repository and try this out for yourself. The file PACKAGING.md contains the instructions you need.
Rough Edges: Windows
Out of the three platforms, I had most difficulties with Windows, perhaps due to my lack of familiarity with the platform. The biggest shortcoming of the packaging process I outline in this post as far as Windows is concerned is the need to run Inno Setup manually afterwards. In theory it should be possible to get the Java Packager to do this and thus avoid the extra step. I didn’t have time to find a solution to this and since I had the workaround of running Inno Setup manually, I was able to manage without. As a second best to getting Java Packager to run Inno Setup directly, it should be possible to automate this step with Gradle to avoid the need to run it by hand.
The problem described earlier in this article relating to MSVCP140.dll
and VCRUNTIME140.dll
is an added complexity when working with Windows. Similarly, the need to copy the Java Packager files into specific locations in the JDK makes things harder for people building Santulator.
Finally, the configuration for the Santulator Inno Setup needs some work. It would look much better if the Santulator icon were to appear there rather than a generic image:
Rough Edges: Mac
The Mac bundle comes as a .pkg
installer. A full installer seems a little unnecessary for a self-contained application. Arguably it would be better to distribute Santulator as a .dmg
file. The Java Packager offers the option to do this but when I tried, I was only able to generate a .dmg
file containing a .pkg
file within it, which rather defeats the purpose.
The packaging process correctly associates the .santa
suffix with the Santulator application and assigns the corresponding icon to it. As expected, when the user double clicks a .santa
file, Santulator opens. However, on the Mac the saved session does not open. This is because Santulator never receives the information relating to the file that was opened. This problem only happens on the Mac: on Linux and Windows the session is opened as expected. You can see more details of this in this GitHub issue.
Just like on Windows, the Santulator Mac installer shows a generic icon instead of the Santulator icon. It’s a minor point but it would be nice to see the program icon there:
Rough Edges: Linux
Saved .santa
files are correctly associated with Santulator but for some reason the session file icon is not shown in the Ubuntu file browser. Take a look at the following for comparison:
The Linux .deb
bundle installs without problems from the command line. However, double clicking the .deb
file doesn’t give a very good user experience as the following screenshot from Ubuntu 18.04 shows:
Finally, when running the Java Packager on Linux, a warning is emitted to say that a licence file should be included. I hope to get some time to investigate this and include the information that Santulator uses the Apache 2.0. Open Source licence.
What the Future Might Hold
Santulator is under active development so this article represents a snapshot in time. Over time the packaging will improve and some of the wrinkles identified here will be ironed out. Similarly, the Java Packager used here was a back port to JDK 11 and in the future it will be improved and will be included in the JDK itself.
Santulator will evolve to take advantage of more JDK 11 features which I hope will in turn prove to be beneficial to the end user. Please do fork the Santulator repository and follow the progress there.
JavaFX
Santulator has a JavaFX user interface that works well on Mac, Linux and Windows. For this reason it makes a lot of sense to offer an installable bundle in order to make it as easy to install as it is to use and the Java Packager offers this. However, it should be noted that the Java Packager isn’t just intended for JavaFX-based applications and so it may also be applicable in your JDK 11 software even if you don’t use JavaFX.
This article doesn’t go into detail about JavaFX but if you are interested in learning more about the subject, you might want to take a look at my article “How JavaFX was used to build a desktop application”. The article is based on my experience of creating the VocabHunter application for learners of foreign languages.
What to Do If You Haven’t Upgraded to JDK 11 Yet
If you haven’t upgraded your Java application to JDK 11 yet but would still like to package it, you could well be in luck. Firstly, you might find that after updating all of your dependencies, with a few small changes your application will run as expected on JDK 11. If it’s a JavaFX application you’ll need to do what I describe in this article and make use of the OpenJFX 11 Java modules provided by Gluon. Doing this will get you all of the benefits of the latest JDK, the Java Packager and the chance to reduce the size of the installable bundle thanks to JLink.
If you need to stick with, for example JDK 8, you can use the version of the Java Packager that comes with the JDK. However, Danny Althoff has written the very useful javafx-gradle-plugin and javafx-maven-plugin for Gradle and Maven, respectively. I would recommend using these plugins instead of directly calling the Java Packager on JDK 8. Version 1.0.23 of VocabHunter is still based on JDK 8 and you can find an example of the use of the Gradle plugin for creating an installable bundle in build.gradle.
Final Thoughts
I hope that this article has inspired you to give the Java Packager a try on JDK 11. Also, as the author of Santulator I’d like to invite you to download the program and try it out. It’s free and Open Source. Feel free to fork the Santulator repository on GitHub and play around with the code to see how it works. To keep up with the latest developments relating to Santulator and VocabHunter, follow @VocabHunterApp on Twitter.