Distribution of JVM desktop applications
Tags: java, gui, desktop, openwebstart
Published: 2021-02-14 17:44:25

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

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](https://en.wikipedia.org/wiki/Java_applet) 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.

```html
<!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 <abbr title="Java Runtime Environment">JRE</abbr>. 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.
>
> -- https://www.oracle.com/technetwork/java/javase/migratingfromapplets-2872444.pdf

## 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](https://www.oracle.com/java/technologies/javase/jws-archive-download.html) 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](https://karakun.com/) is a company that took ownership of the project. It provides it as a separate package under the name [Open Web Start](https://openwebstart.com/) 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 <abbr title="Java Network Launching Protocol">JNLP</abbr> 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.

The complete [specifications](https://docs.oracle.com/javase/tutorial/deployment/deploymentInDepth/jnlpFileSyntax.html) are still available on Oracle's site.

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

```xml
<?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`](https://docs.oracle.com/en/java/javase/14/jpackage/packaging-overview.html) 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:

![Application's structure as created by the jpackage](https://dev-to-uploads.s3.amazonaws.com/i/afy1j6yy5u40zqmqxgam.jpg)

The structure includes:

* 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](https://medium.com/azulsystems/using-jlink-to-build-java-runtimes-for-non-modular-applications-9568c5e70ef4), 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:

```java
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](https://blog.frankel.ch/update-state-java-modularization/) 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](https://blogs.oracle.com/javamagazine/containerizing-apps-with-jlink). Suffice to say here that it makes heavy use of the [Moditect Maven plugin](https://github.com/moditect/moditect).

After having [configured the POM](https://github.com/ajavageek/renamer-swing/blob/feature/jlink/pom.xml#L56-L108), packaging the Renamer Swing application yields a folder that contains a runnable launcher script and the necessary modules from the JDK.

![Output of jlink](https://dev-to-uploads.s3.amazonaws.com/i/0ik3i9tv47x00fh30laz.jpg)

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`](https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/packager.html) 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](https://github.com/edvin/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](http://launch4j.sourceforge.net/):

    > 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](https://github.com/libgdx/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](https://www.ej-technologies.com/products/install4j/overview.html):

    > 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:**

* [Applets](https://www.oracle.com/java/technologies/applets.html)
* [Java Web Start on Wikipedia](https://en.wikipedia.org/wiki/Java_Web_Start)
* [Open Web Start](https://openwebstart.com/)
* [A Guide to jpackage in Java 14](https://www.baeldung.com/java14-jpackage)
* [Guide to jlink](https://www.baeldung.com/jlink)

_Originally published at [A Java Geek](https://blog.frankel.ch/state-jvm-desktop-frameworks/6/) on February 14<sup>th</sup> 2021_