A quick look at jpackage
Tags: java
Published: 2019-12-26 13:39:42

With Java 14, we will get back a tool to create native installation archives for our Java applications. While Java on the client unfortunately is not considered a hot topic these days, it is very important that the current gap is closed - again. There used to be a commandline tool called `javafxpackager` which was later renamed `javapackager` and even later removed from the JDK. java(fx)packager performed tasks related to packaging and signing Java and JavaFX applications, so when JavaFX was unbundled from the JDK, it sort of made sense to temporarily remove related tools as well. With `jpackage` a fresh, re-thought and more general implementation returns. 
Java 14 will be released in March 2020 so what is available today is preliminary (early access). Further, jpackage is considered an incubator feature, so even what we will see in March may change over time. 
Installing Java on a Mac is a matter of a few minutes. After downloading and unzipping the tarball just move the base directory to `/Library/Java/JavaVirtualMachines/`.
```
cd ~/Downloads
tar xzf openjdk-14-ea+28_osx-x64_bin.tar.gz
sudo mv jdk-14.jdk /Library/Java/JavaVirtualMachines
```
Did you know you can list your installed Java versions with `/usr/libexec/java_home -V`? By default the Mac will use the latest version, so after this installation `java -version` will print something like _openjdk version "14-ea" 2020-03-17_. Still, typing `jpackage` will produce a _command not found_. To fix this, you could add `/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home/bin` to `PATH`. For now I suggest to just use the absolute path when invoking the tool, because you will pass quite a few arguments, so creating a small shell script will make your life much easier.

Take a look at this one:
```
#!/bin/sh
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-14.jdk/Contents/Home
BASEDIR=/Users/thomas/Entwicklung/Bitbucket/clip4moni
VERSION=`sed -n -e 's/.*VERSION = \"\(.*\)\".*/\1/p' < $BASEDIR/src/main/classes/com/thomaskuenneth/clip4moni/Clip4Moni.java`

$JAVA_HOME/bin/jpackage --name Clip4Moni --icon $BASEDIR/artwork/Clip4Moni.icns --app-version $VERSION --type app-image --module-path $BASEDIR/build/modules -m main/com.thomaskuenneth.clip4moni.Clip4Moni
```

I use it to create the application bundle for my app __Clip4Moni__, a small text snippet tool that resides in the system tray (Windows, Linux) or menu bar (on a Mac). It is based upon Java modules. While `jpackage` can work with plain jars as well, I strongly suggest bringing your code to recent Java versions. So many nice features have gone into the language, it is more than a pity not to use them. Now, let's go through the script, starting with the last line. `--name` is the name of the application. `--icon` references the icon file. `--type` defines what kind of archive shall be created, in this case it is just the app bundle. `--module-path` specifies where the module files are located. `-m` identifies the main class.

`--app-version` sets, well, the version number of the app, for example `1.3.3`. If you look at my script I do a funny looking `sed` call. Let me explain. I define the app version as a `String` constant. So I go to the source file in which the definition resides, and read the version number. This is the input:
```
...
public static final String VERSION = "1.3.3";
...
```

`jpackage` is easy to use. Although it is still in preview it worked flawlessly. There is just a minor thing I noticed. Part of a Mac app bundle is a file called `info.plist` that contains important information about the app. It is created by jpackage. Currently it contains these lines:
```
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
<key>LSApplicationCategoryType</key>
<string>Unknown</string>
```

The underlying idea seems to be that developer edits the file. But using the syntax in my script, it is created on the fly. One solution might be to edit it after the invocation of `jpackage`. But this may not work if you sign your app (which can be done through `jpackage`). Another thing I would love to see is the ability to bring in additional entries. My app resides in the menu bar so I would like to hide the Dock icon. This is done as follows:
```
<key>LSUIElement</key>
<string>true</string>
```
It would be cool to specify such entries at the commandline. There may already be solutions to this, so if you, dear reader, know how to do this, please tell me in the comments.