Jar2Exe #1: Creating Custom Runtime Image

4 minute read

Part 2 Wrap JAR and JRE Together

Introduction

Java 9 introduced a new feature in programming in this language, which is the ability to create modules. By module, we mean a set of packages together with a file describing its content (descriptor).

A module is essentially a set of packages that make sense being grouped together and is designed for reuse.
blogs.oracle.com/java/modular-development

Java Platform Module System

The main purpose of the Java Platform Module System (JPMS) is to reduce the size of the application. Thanks to that, we can run virtual machines on devices with significantly smaller resources, which gives us an increase in performance. But how was this achieved? The answer is: from Java 9 we can isolate and compile application modules along with determining the dependencies required and provided by the module. This replaces the need to attach all JARs to each application separately. Instead, we build Java runtimes that is tailored to the specific application (only required items are included).

It is worth noting that modules are not a mandatory element of programs written in newer versions of JDK, but only an additional option. An example of the program on which this tutorial will be based is my CLP Calculator (download here). It’s built on JDK 11 and OpenJFX. I remind you that from version 11 the JavaFX platform for GUI creation is no longer an integral part of JDK and must be downloaded separately. The whole process of such configuration was described in the previous post.

The tool that will allow to generate a runtime image is called “jlink” (available from Java 9). It should be noted that jlink works with modules. What about applications that migrate, e.g. from JDK 8 to JDK 11? Fortunately, all the application code along with all external libraries can be treated as part of the module with no name.

Custom Runtime Image on Windows x86

As you can see, both the new JDK versions and OpenJFX are only available for Windows 64-bit. Generated a runtime image will only support this version of systems. So what about the 32-bit ones?

First you need to look for JDK that will work on these versions of the systems. Since Java 9, there are no official sources that are compatible with 32-bit architecture. Fortunately, you can find other companies that share this: Zulu OpenJDK or Liberica JDK.

A similar situation applies to OpenJFX. There are no official sources for such systems. Instead, you can find instructions that will allow to build your own version. The easiest way is to use the community - GitHub discussion (download openjfx11_x86.zip → all credit to @graynk).

Some unofficial versions of JDK have JavaFX built in[1] (e.g. Zulu 11.33.15 and Liberica 12.0.2). You can check this by looking for the following jmod files in their directory:

  • javafx.base.jmod
  • javafx.controls.jmod
  • javafx.fxml.jmod
  • javafx.graphics.jmod
  • javafx.media.jmod
  • javafx.swing.jmod
  • javafx.web.jmod

Getting Started

Let’s start by running application from Command Prompt. As I mentioned before, you will need the JAR file of final program. If your application does not use JavaFX platform or uses it, but it was built on JDK up to version 10, all you have to do is open Command Prompt, go to the location on the disk with the JAR file of your application and enter the following command:

java -jar CLP-Calculator.jar

It also works on some unofficial versions of JDK 11+ [1].

In my case, the application is built using JDK 11 & OpenJFX. JavaFX is not part of JDK 11, so I must indicate the location of my GUI creation platform in the command.

java --module-path "C:\Program Files\Java\javafx-sdk-11.0.2\lib" --add-modules=javafx.controls,javafx.swing,javafx.fxml -jar CLP-Calculator.jar

As an explanation:

  • “module-path” - provides information about the location of the module;
  • “add-modules” - specifies additional root modules.


Note!

  1. “javafx.swing” is required in my case because CLP Calculator uses the class “SwingNode” in the code, which together with related “SwingNodeHelper” are part of the javafx.swing module. Therefore, to indicate that the class is to be used with a modulepath and not with classpath, I must include this information in “add-modules”.
    If your program does not use the “SwingNode” class, you can skip this section. Conversely, if your program uses any class that should be used from the appropriate modulepath, add this information here.
  2. A similar command was entered when setting the VM options in IntelliJ IDEA (here).

First you must find out which JDK modules the application needs. For this purpose, you will use another tool called “jdeps”.

Open the Command Prompt, go to the place where your JAR file is and enter the following command:

jdeps --list-deps CLP-Calculator.jar
result

Unfortunately, this command does not contain OpenJFX modules, so let’s modify it:

jdeps --module-path "C:\Program Files\Java\javafx-sdk-11.0.2\lib" --add-modules javafx.controls,javafx.swing,javafx.fxml --list-deps CLP-Calculator.jar
result

You can generate a runtime image now. To do this you have to:

  • put all displayed dependencies after “add-modules” (including javafx.controls, javafx.swing, javafx.fxml);
  • copy all jmod files from JavaFX SDK (“Java\javafx-sdk-11.0.2\lib\javafx-jmods-11.0.2”) to JDK directory (“Java\jdk-11.0.2\jmods”). For [1] this is unnecessary.
jlink --bind-services --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.datatransfer,java.desktop,java.scripting,java.xml,javafx.graphics,javafx.controls,javafx.swing,javafx.fxml --output jre

To optimize the size of the generated JRE, I added a few more flags:

  • “no-header-files” - excludes header files;
  • “no-man-pages” - excludes man pages;
  • “compress=2” - enable compression of resources ZIP;
  • “strip-debug” - strips debug information from the output image;
  • “bind-services” - link service provider modules and their dependencies. In my case this is required because there is a bug in OpenJDK 11.

You can find more options here.

Run Your App

Custom runtime image was generated in the same folder as the JAR file. Let’s move this CLP-Calculator.jar to “jre\bin” and try to run application.

java -jar CLP-Calculator.jar
result

The entire “jre” folder can be moved to another computer with the right system architecture (any 32-bit program runs on 64-bit) and JDK installed (Java Runtime Environment is not needed). In the next post I will show you how to make an EXE from application, which you will not need even JDK to run.

Leave a comment