How to deploy a JavaFX 11 Desktop application with a JRE

The way it works now is, you convert your program to a module, then “link” it to all the other modules it requires.

The result of this linking process is what’s known as an image. An image is actually a file tree, which includes a bin directory with one or more ready-to-run executables. This tree is what you distribute, typically as a zip or tar.gz.

The steps are:

  1. Create a module-info.java
  2. Compile with a module path instead of a classpath
  3. Create a jar from classes, as usual
  4. Convert jar to a jmod with the JDK’s jmod tool
  5. Link that jmod, and the modules on which it depends, into an image

Writing a module descriptor

The first step is to turn the application into a module. Minimally, this requires creating a module-info.java at the top of your source tree (that is, in the empty package). Every module has a name, which is often the same as a package name but doesn’t have to be. So, your module-info.java might look like this:

module com.mcs75.businessapp {
    exports com.mcs75.desktop.businessapp;

    requires java.logging;
    requires transitive javafx.graphics;
    requires transitive javafx.controls;
}

Building

When you build, you don’t specify a classpath at all. Instead, you specify a module path.

A module path is a list of directories, not files. Each directory contains, not surprisingly, modules. The jmods directory of the JDK is included implicitly. All you need to include is directories containing the non-JDK modules you need. In your case, that at least means Gluon’s JavaFX:

javac -Xlint -g -d build/classes --module-path /opt/gluon-javafx/lib \
    src/java/com/mcs75/desktop/businessapp/*.java

Then you create a jar the usual way:

jar -c -f build/mybusinessapp.jar -C build/classes .

A jar file with a module-info.class in it is considered a modular jar.

Making a jmod

Creating a jmod is usually a simple process:

mkdir build/modules
jmod create --class-path build/mybusinessapp.jar \
    --main-class com.mcs75.desktop.businessapp.BusinessApplication \
    build/modules/mybusinessapp.jmod

Linking

Finally, you use the JDK’s jlink command to assemble everything:

jlink --output build/image \
    --module-path build/modules:/opt/gluon-javafx/lib \
    --add-modules com.mcs75.businessapp \
    --launcher MyBusinessApp=com.mcs75.businessapp

jlink creates a minimal JRE, which contains only the modules you explicitly add (and the modules those explicit modules require). --add-modules is the required option that specifies what to add.

As with other JDK tools, --module-path specifies directories containing modules.

The --launcher option causes the final image tree to have an additional executable script in its bin directory with the given name (the part before the equals). So, MyBusinessApp=com.mcs75.businessapp means “create an executable named MyBusinessApp, which executes the module com.mcs75.businessapp.”

Because the jmod create command included a --main-class option, Java will know what to execute, just like declaring a Main-Class attribute in a manifest. It is also possible to explicitly declare a class to execute in the --launcher option if desired.

Distributing

What you will want to distribute is a zip or tar.gz of the entire image file tree. The executable that the user should run is in the image’s bin directory. Of course, you are free to add your own executables. You are also free to place this into any kind of installer, as long as the image tree’s structure is preserved.

A future JDK will have a packaging tool for creating full-fledged native installers. As of Java 14, the JDK has a jpackage tool which can create native installers. For example:

jpackage -n MyBusinessApp --runtime-image build/image \
    -m com.mcs75.businessapp/com.mcs75.desktop.businessapp.BusinessApplication

-n specifies the name of the program. --runtime-image specifies the location of the existing jlink’d image. -m is the module and class within the jlink’d image to execute, much like the portion after the = in jlink’s --launcher option.

Cross-building

Since the image includes native binaries, you will need to create an image for each platform. Obviously, one option is to build the image on a Linux system, and again on a Windows system, and again on a Mac, etc.

But you can also use jmod and jlink to create images for other platforms, regardless of where you’re building.

There’s only a few additional steps needed. First, you will need the JDKs for those other platforms. Download them as archives (zip or tar.gz), not installers, and unpack them to directories of your choice.

Each JDK defines a platform string. This is normally of the form <os>-<arch>. The platform is a property of the java.base module; you can see any JDK’s platform by examining that module:

jmod describe path-to-foreign-jdk/jmods/java.base.jmod | grep '^platform'

Pass that platform string to your jmod command using the --target-platform option:

mkdir build/modules
jmod create --target-platform windows-amd64 \
    --class-path build/mybusinessapp.jar \
    --main-class com.mcs75.desktop.businessapp.BusinessApplication \
    build/modules/mybusinessapp.jmod

Finally, when linking, you want to explicitly include the other JDK’s jmods directory, so jlink won’t implicitly include its own JDK’s modules:

jlink --output build/image \
    --module-path path-to-foreign-jdk/jmods:build/modules:/opt/gluon-javafx-windows/lib \
    --add-modules com.mcs75.businessapp \
    --launcher MyBusinessApp=com.mcs75.businessapp

Leave a Comment