Bundle A Java 11 App Into A Minimal JRE (without Module System Adaptations)
Requirements
Gradle 4.8 or newer:
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 !
:
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)
The build/image
directory will be generated.
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.
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:
- Append to your runtime block a javaHome property:
runtime { // [...] javaHome = System.getProperty("org.gradle.java.home") }
- 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:
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:
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
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:
Soufiane Sakhi is an AWS Certified Solutions Architect – Associate and a professional full stack developer.