Distribution of JVM desktop applications

The previous posts of this series focused on different frameworks to develop JVM-based applications.

This is the 6th post in the The state of JVM desktop frameworks focus series. Other posts include:

  1. The state of JVM desktop frameworks: introduction
  2. The state of JVM desktop frameworks: Swing
  3. The state of JVM desktop frameworks: SWT
  4. The state of JVM desktop frameworks: TornadoFX
  5. The state of JVM desktop frameworks: Jetpack Compose for Desktop

Distributing applications on a couple of computers inside the same company is not an issue. A lot of products are available for automating the pushing of files onto computers. Issues might start to appear when you need to coordinate the deployment across different physical sites.

However, the biggest problem is when computers are not known in advance: in that case, it’s not possible to push. For that reason, two decades ago, we saw a move from client-server architectures to web-based architectures.

The remaining question is how to deploy a Java desktop application. In this post, we are going to go through some options to distribute this kind of application.

Applets

While not strictly about desktop applications, I believe it’s a good idea to mention applets in a post about distributing Java apps.

Applets were the first way to make Java applications available remotely. The idea was to host on a web server both bytecode (a single class or JAR) and reference it in an HTML page.

<!DOCTYPE html>
<html>
<body>
<applet code="HelloWorld.class" height="640" width="480"></applet>
</body>
</html>

Note that an applet runs client-side, thus requiring a local JRE. It also mandates a browser plugin. Both are security concerns. That lead browser vendors to remove the support of plugins and in turn the Java team to deprecate applets in Java 9.

With modern browser vendors working to restrict or reduce the support of plugins like Flash, Silverlight, and Java in their products, developers of applications that rely on the Java browser plugin need to consider alternative options. Java developers currently relying on browser plugins should consider migrating from Java Applets to the plugin-free Java Web Start technology.

Supporting Java in browsers is only possible for as long as browser vendors are committed to supporting standards-based plugins. By late 2015, many browser vendors had either removed or announced timelines for the removal of standards-based plugin support, while some are introducing proprietary browser-specific extension APIs. Consequently, Oracle is planning to deprecate the Java browser plugin in JDK 9.

The deprecated plugin technology will be completely removed from the Oracle Java Development Kit (JDK) and Java Runtime Environment (JRE) in a future Java release TBD. Java Web Start applications do not rely on a browser plugin and will not be affected by these changes.

Migrating from Java Applets to plugin-free Java technologies

Open Web Start

After applets, the next alternative for distributing Java applications is Java Web Start. This is the canonical way for desktop applications. As a personal note, I used it more than 15 years ago with Java 1.4. This page seems to confirm JDK 1.4.2 was the first version to provide it natively, as opposed to as a separate download. Yet, Oracle deprecated Java Web Start in JDK 9 and removed it in JDK 11.

This left JWS users without any option. Karakun is a company that took ownership of the project. It provides it as a separate package under the name Open Web Start and the software is the same.

To distribute an application via Open Web Start, in addition to the JAR itself, you need to provide a JNLP file. This file serves as an external deployment descriptor of a sort. Features of JNLP are quite exhaustive:

  • Add a shortcut on the user’s desktop
  • Whether the application can run offline after you’ve downloaded it
  • Update policies
  • Optional list of native libraries
  • etc.

Let’s create a JNLP to distribute our sample Swing application:

<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="" href="">
<information>
<title>File Renamer</title>
<vendor>Nicolas Fränkel</vendor>
<offline-allowed/>
</information>
<resources>
<j2se version="11+" />
<jar href="https://path.to/archive.jar" <!--1-->
main="true" /> <!--2-->
</resources>
<security>
<all-permissions /> <!--3-->
</security>
<application-desc name="File Renamer"
main-class="ch.frankel.blog.renamer.RenamerAppKt" /> <!--4-->
<update check="background"/>
</jnlp>
  1. Path to the JAR
  2. Flag that marks the JAR as containing the main class
  3. The Renamer application reads and writes the filesystem, it requires permissions. Note that permission grant is not fine-grained. Also, permissions require you to sign the JAR.
  4. Class that contains the entry-point (the main method)

jpackage

While Open Web Start is not available as part of the JDK anymore, the jpackage tool is. It's included since JDK 14. As its name implies, jpackage allows you to create a native installer from a JAR.

Its basic usage is straightforward:

jpackage --input target --main-jar renamer-swing-1.0.jar (1)
  1. Assume a Maven project that generates the JAR in the target subfolder

On a macOS computer, the above command creates a RenamerAppKt-1.0.dmg installer. It contains a macOS application with the following structure:

  • The JAR
  • A JRE — the one associated with the jpackage command that created the installer
  • macOS-specific files

While the sample JAR is 1.5 MB, the installer is more than 50 MB because of the embedded Java runtime.

jlink

jlink is not a real distribution mechanism but I believe it still deserves a mention. It allows you to create a custom runtime with a dedicated launcher script for your applications. It's part of the JDK since Java 9.

While it can in theory work with any application, it shines with modularized applications. Such applications need to describe the modules they depend on in a module-info.java.

The main task is to modularize the application. For our sample app, the module-info.java file is:

module filerenamer.swing {
requires eventbus; // 1 2
requires java.desktop; // 1
requires kotlin.stdlib; // 1
exports ch.frankel.blog.renamer to eventbus; // 3
}
  1. Declare dependent modules
  2. The Greenrobot library is an unnamed module. We need to reference it via the JAR’s name.
  3. Allow Greenrobot to use our classes

Modularizing one’s application is pretty straightforward. But to decide whether a JDK module will make it into the final runtime, jlink requires to transitively analyze every dependency of the application. Yet, about 35% of the libraries are neither modularized nor declare an Automatic-Module-Name in their MANIFEST.MF.

It’s possible to add this information during one’s build even though the procedure is error-prone and boring. The description on how to achieve this deserves a post on its own. For more information, please check this Oracle magazine article. Suffice to say here that it makes heavy use of the Moditect Maven plugin.

After having configured the POM, packaging the Renamer Swing application yields a folder that contains a runnable launcher script and the necessary modules from the JDK.

The folder size is around 86MB. By using gzip, you can reduce the size to about a 30MB archive that you can distribute.

Note that it will require users to decompress the archive and to know which script to launch, though.

Other candidates

Here are some other candidates that I discovered while researching this post. Note that I have experience with none of them but it might be useful for some of you.

  • Oracle introduced javapackager in Java 8. javapackager is the ancestor of jpackage and is now superseded by the latter.
    The Java Packager tool can be used to compile, package, sign, and deploy Java and JavaFX applications from the command line. It can be used as an alternative to an Ant task or building the applications in an IDE.
  • FXLauncher is both a launcher and an auto-updater that focuses solely on JavaFX applications.
    FXLauncher is released under the Apache License 2.0.
  • Launch4J:
    Launch4j is a cross-platform tool for wrapping Java applications distributed as jars in lightweight Windows native executables. The executable can be configured to search for a certain JRE version or use a bundled one, and it’s possible to set runtime options, like the initial/max heap size. The wrapper also provides a better user experience through an application icon, a native pre-JRE splash screen, and a Java download page in case the appropriate JRE cannot be found.
    Launch4J is released under the BSD 3-Clause License but has not seen any release since 2017.
  • packr:
    Packages your JAR, assets, and a JVM for distribution on Windows, Linux, and macOS, adding a native executable file to make it appear like a native app. Packr is most suitable for GUI applications, such as games made with libGDX.
    packr is released under the Apache License 2.0.
  • Install4J:
    install4j is a powerful multi-platform Java installer builder that generates native installers and application launchers for Java applications.
    install4J is a proprietary product.

Conclusion

In this post, we browsed through several options to distribute our Java desktop applications:

  • Open Web Start is an option based on the former Java Web Start. Users need to install the software on their system so they can execute JNLP files. Noteworthy features include auto-updating and offline mode.
  • JDK includes jpackage. It allows you to create installers that wrap both the JAR and a JRE.
  • jlink is also included in the JDK. Instead of creating installers, it creates custom distributions that you need to package yourself.

To go further:

Originally published at A Java Geek on February 14, 2021.

Dev Advocate @Hazelcast . Former developer and architect. Still teaching, learning and blogging.