Bundle A Java 11 App Into A Minimal JRE (without Module System Adaptations)

Run Java apps without installing JDK or JRE
Generate a self-contained Java application package that includes a custom runtime environment containing the necessary JDK modules and components needed to run your app, without requiring users to install Java, using Gradle and the Badass Runtime Plugin

Requirements

  • Gradle 4.8 or newer:

    • Refer to the following guide if you are not already using Gradle
    • To migrate from Apache Maven instead, see this guide
  • JDK 11 or newer (get started with Java 11 with the following guide)

  • You're app must be using Java 11 or newer. To migrate from Java 8:

    • Update all your dependencies to their latest version

    • Add third-party dependencies to replace code relying on technologies removed in JDK 11 (symptoms: java.lang.NoClassDefFoundError exceptions at runtime). Examples:

      dependencies {
          // [...]
      
          // JAXB
          implementation 'org.glassfish.jaxb:jaxb-runtime:2.4.0-b180830.0438'
      
          // JAX-WS
          implementation 'com.sun.xml.ws:jaxws-ri:2.3.2'
      }
    • Replace dependencies on internal APIs with alternative code (symptoms: package is not visible compile errors)

Module declaration files (module-info.java) are not required.

Getting started

In this tutorial, we will be using the Badass Runtime Gradle plugin. Behind the scenes, it uses the jlink JDK tool to generate the final slim runtime image.

Let's begin with this simple Java project that just prints Hello World !:

1. Hello World structure

Gradle configuration

Apply the Badass Runtime plugin to your project's build.gradle:

plugins {
    // [...]
    id "org.beryx.runtime" version "1.3.0"
}

(The latest version can be found here)

Append the following block to your build.gradle:

runtime {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}

If you have any resources, they must be copied to the generated runtime image by appending the following block to the build.gradle:

tasks.runtime.doLast {
    copy {
        from('src/main/resources')
        into("$buildDir/image/bin")
    }
}

Generate the custom runtime image

In this example, the JAVA_HOME environment variable is assumed to be pointed at a JDK 11 (The following section will describe how to use another JDK).

From the command line, run gradlew runtime (or gradle runtime when using a global Gradle installation)

2. gradlew runtime

The build/image directory will be generated.

3. build image directory

This directory contains all the necessary modules and files to run the Java app without needing to install a JDK or JRE in the user's workstation.

Note that inside the bin directory, there are 2 executable scripts that can be used to launch your Java app: <PROJECT_NAME>.bat and <PROJECT_NAME>. The .bat file can be used to launch your app on Windows, and the second one can be used for other platforms such as Linux.

4. Project launch

Badass Runtime Plugin configuration

Custom JAVA_HOME

If your JAVA_HOME points to a JDK 8 or if you simply want to use an alternative JDK 11 or newer:

  1. Append to your runtime block a javaHome property:
     runtime {
         // [...]
         javaHome = System.getProperty("org.gradle.java.home")
     }
  2. Generate the runtime image with the command:
         gradlew runtime -Dorg.gradle.java.home="/your/jdk11/path"

Custom modules

To modify the JDK modules that will be bundled with your runtime, you can use the modules property:

runtime {
    // [...]
    modules = ['java.naming', 'java.xml']
}

To list the modules used by your Java app, you can use the suggestModules task:

5. suggestModules

Other modules will be required to be added if any java.lang.NoClassDefFoundError exception is displayed at runtime.

The jdeps JDK tool can be used for further module analysis.

References

Spring Boot example

I generated a Spring Boot app for a weather API by following this tutorial.

After applying all the steps from the first section, the following error was displayed when launching the app with the custom runtime:

6. Missing module error

The dependencies must be analyzed to find the missing required modules.

The build/install/<APP_NAME>/lib directory was generated previously by the runtime task and contains all dependency jars of the Java app. The jdeps tool can be used in this case to analyze the missing required modules with the command:

"%JAVA_HOME%/bin/jdeps" --multi-release 11 --print-module-deps build\install\<APP_NAME>\lib\spring*.jar build\install\<APP_NAME>\lib\tomcat*.jar

7. jdeps output

The modules property of the runtime Gradle task of the spring boot app was updated as follows:

runtime {
    // [...]
    modules = ['java.base','java.desktop','java.instrument','java.management.rmi','java.naming','java.prefs','java.scripting','java.security.jgss','java.sql','jdk.httpserver','jdk.unsupported']
}

After executing the gradlew runtime command and launching the app with the script, the errors are gone:

8. spring boot runtime image


Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer.