Liberica NIK: Using Native Image with desktop applications

1. Introduction

This document will guide you through generating native images out of desktop applications built with JavaFX and AWT.

2. How to create JavaFX native images

Enable native compilation

First, you need to download and install Liberica NIK. Go to the Liberica NIK Download Center and choose NIK version 22 for Java 11 or 17. You need the Full version as it includes LibericaFX, an instance of OpenJFX.

Download the package and follow the instructions to install the utility on your platform. macOS users should get the .dmg package to avoid any issues with running Liberica NIK.

You are now ready to compile Java applications natively. You have several options:

In all three cases, your application is likely to use resources such as icons or images. These resources must be made known to native-image so that it includes them in the resulting executable. There are two options to include and exclude resource identifiers, and you can use wildcards there:

-H:IncludeResources='com.fxapp.resources.images.*'
-H:ExcludeResources='com.fxapp.resources.unused.*'

Alternatively, resources can be configured using JSON files. Many applications make use of dynamic language features such as reflective or JNI access. In this case, the feature being used, such as the name of the method called, is not known at image build time, so NIK cannot figure out exactly which method is called. These dynamic feature usages must be configured beforehand using JSON files as described here.

You can create JSON files using the native image agent. Run your application with the agent enabled, probably several times to exercise different code paths. The agent captures dynamic feature accesses and creates configuration files automatically. These files can be passed to native-image without further editing.

Compile the application

Let us now compile a JavaFX app natively. We will use the BrickBreaker game as an example. You can utilize your own app or select a JavaFX demo on GitHub. BrickBreaker requires no dynamic feature configuration. At the same time, it uses lots of images that need to be included in the resulting executable. In this example, image names are passed using -H:IncludeResources with a regular expression:

native-image -H:IncludeResources='ensemble.samples.shared-resources.brickImages.*' -jar BrickBreaker.jar

After the compilation has finished, start the application:

./BrickBreaker

Speed and memory comparison

The figures below were obtained on an Intel Core i7 laptop running Ubuntu Linux.

Liberica JRE 17.0.4.1Native Image made with Liberica NIK 22.2.0-JDK17

Startup time

710ms

366ms

Maximum RSS

176M

133M

Executable size

276M (JRE+jar)

36M

Limitations

Modules javafx.media and javafx.web are currently not supported for native compilation. The javafx.media module is responsible for adding the playback of media and audio content. The javafx.web module defines the WebView functionality.

3. How to turn AWT applications into native images

AWT (the Abstract Window Toolkit) is a Java GUI widget toolkit. Although Swing largely superseded AWT, the latter is still used alone or in combination with Swing components.

Install Liberica NIK

The Liberica NIK Full version can be used to turn AWT/Swing applications into native images on Linux, Windows, and macOS.

For our demo, we will use Liberica NIK 22.3.1 for Java 17. Download the utility for your platform and follow the instructions to complete the installation. Set $PATH to Liberica NIK:

PATH=<path-to-nik>/bin:$PATH

Create an AWT app

Build a simple AWT application (the code was taken here):

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class Sample extends Frame {
  public Sample() {
    Button btn = new Button("Button");
    btn.setBounds(50, 50, 50, 50);
    add(btn);
    setSize(150, 150);
    setTitle("Simple AWT window");
    setLayout(new FlowLayout());
    setVisible(true);
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent we) {
        dispose();
      }
    });
  }
  public static void main(String args[]){
    new Sample();
  }
}

Add a maven plugin to build an executable jar:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-assembly-plugin</artifactId>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
          <configuration>
            <archive>
              <manifest>
                <mainClass>
                  Sample
                </mainClass>
              </manifest>
            </archive>
            <descriptorRefs>
              <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Go to the project directory and build a jar file with mvn package.

Build an AWT native image

You need to set java.awt.headless property to false.

Build a native image as follows:

native-image -Djava.awt.headless=false -jar target/AwtDemo-1.0-SNAPSHOT-jar-with-dependencies.jar awtdemo

Run the image with the following command:

./awtdemo

If your app does not use any resources, Reflection, JNI, or other features not supported by GraalVM, you don’t need to configure anything. Otherwise, explicitly provide resources or any classes used by reflection or serialization to the native-image tool. For that purpose, run the application with Graal tracing agent to dump the resources and dynamic classes used by the application. After that, run the native-image tool with configuration files that you generated.

ON THIS PAGE