1. Background and Issues
The traditional JDK/JRE installation packages are large (hundreds of MB), containing many modules that are not needed by applications. For example, a small application that only depends on java.base and java.sql must carry the entire Java runtime, which not only wastes storage but also affects the distribution and startup speed of the application.
JLink (introduced in JDK 9) was created to solve this problem. It allows developers to customize and build a Java runtime that only includes the required modules, thus generating a lightweight, dedicated JRE.
2. What is JLink?
-
Definition: JLink is a command-line tool provided by the JDK for generating a custom runtime image based on the actual modules needed by the application.
-
Location: Located in the
<span>bin/</span>directory of the JDK (alongside<span>javac</span>and<span>java</span>). -
Core Objectives:
- Minimize size
- Faster loading
- Easy distribution
3. Basic Usage
3.1 Viewing Installed Modules
java --list-modules
This will list all the modules built into the current JDK, for example:
java.base@17
java.sql@17
jdk.compiler@17
3.2 Building a Custom Runtime
Assuming our application only depends on <span>java.base</span> and <span>java.sql</span>, we can execute:
jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base,java.sql \
--output custom-runtime
After execution, a runtime directory named <span>custom-runtime</span> will be generated in the current directory, with a structure similar to a lightweight JRE:
custom-runtime/
├─ bin/
├─ conf/
├─ include/
├─ legal/
└─ lib/
4. Common Options
| Parameter | Description |
|---|---|
<span>--module-path</span> |
Module path, usually pointing to <span>$JAVA_HOME/jmods</span> |
<span>--add-modules</span> |
Specifies the list of modules to be packaged |
<span>--output</span> |
Specifies the generated runtime directory |
<span>--compress=2</span> |
Uses a stronger compression algorithm to reduce size |
<span>--strip-debug</span> |
Removes debug information to further reduce size |
<span>--no-header-files</span> |
Does not include header files |
<span>--no-man-pages</span> |
Does not include man pages |
Example (minimizing runtime):
jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base \
--output tiny-runtime \
--strip-debug \
--no-header-files \
--no-man-pages \
--compress=2
5. Application Scenarios
- Containerized Deployment: Place only the minimal runtime in the Docker image, greatly reducing the image size.
- Embedded Devices: For example, IoT terminals that only require a few Java modules.
- Custom Distributions: Customize a dedicated runtime for a specific application, avoiding unnecessary modules.
6. Relationship with jpackage
- jlink: Generates a lightweight runtime image (equivalent to a slimmed-down JRE).
- jpackage: Can package the runtime generated by jlink into an installable application (e.g.,
<span>.deb</span>,<span>.msi</span>).
Typical workflow: 👉 Use <span>jlink</span> to customize the runtime → Use <span>jpackage</span> to package and distribute
7. What Modules Does the Application Need?
This is a critical question 👍. Before using jlink to build a lightweight runtime, it is essential to clarify what modules the application actually needs, otherwise, missing dependencies will cause runtime errors. Here are a few common methods:
7.1. Dependency Analysis Tool — <span>jdeps</span>
<span>jdeps</span> is a dependency analysis tool included with the JDK that helps identify which JDK modules an application depends on.
Assuming you have an application package <span>myapp.jar</span>:
jdeps --print-module-deps myapp.jar
The output may be:
java.base,java.logging,java.sql
👉 This means your application depends on <span>java.base</span>, <span>java.logging</span>, and <span>java.sql</span> modules.
Then you can directly write in jlink:
jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base,java.logging,java.sql \
--output myruntime
7.2. Automatically Generate Command
If you want a more direct approach, you can let <span>jdeps</span> automatically generate the complete <span>jlink</span> command:
jdeps --print-module-deps --ignore-missing-deps myapp.jar \
| xargs -I {} jlink --module-path $JAVA_HOME/jmods --add-modules {} --output myruntime
This completes both dependency analysis and runtime construction in one step.
7.3. Common Module References
Here are some commonly needed JDK modules for applications (partial list):
- Core Essentials:
<span>java.base</span>(must be included by default) - Logging:
<span>java.logging</span> - Database:
<span>java.sql</span>,<span>java.transaction</span> - XML/JSON:
<span>java.xml</span>,<span>jdk.xml.dom</span> - Concurrency/Utilities:
<span>java.management</span>,<span>jdk.unsupported</span>(may be used by some legacy code) - Security:
<span>java.security.sasl</span>,<span>java.naming</span>
7.4. Verifying Runtime Completeness
After building the runtime, you can directly use it to run the application for testing:
./myruntime/bin/java -jar myapp.jar
If modules are missing, the runtime will prompt with <span>NoClassDefFoundError</span> or <span>ModuleNotFoundError</span>, at which point you can add the necessary modules.
✅ The recommended workflow is:
- Analyze dependencies with jdeps →
- Build runtime with jlink →
- Validate runtime in the target environment
This ensures that the runtime is both streamlined and does not lack dependencies.
8. Using jlink to Optimize Docker Images
In containerized deployments, a complete JDK base image can often exceed 300MB+, which is clearly not “cloud-native” if only a small application needs to run. At this point, you can use jlink to build the minimal runtime and package it into the Docker image.
8.1 Creating a Minimal Runtime
On the host machine or in the CI/CD pipeline, execute:
jlink \
--module-path $JAVA_HOME/jmods \
--add-modules java.base,java.sql \
--output /opt/java-minimal \
--strip-debug \
--no-header-files \
--no-man-pages \
--compress=2
The generated <span>/opt/java-minimal</span> directory is a trimmed Java runtime.
8.2 Dockerfile Example
# Use a slim Linux base image
FROM debian:bullseye-slim
# Copy the lightweight runtime generated by jlink
COPY --from=builder /opt/java-minimal /opt/java-minimal
# Set PATH
ENV PATH="/opt/java-minimal/bin:${PATH}"
# Copy application jar file
COPY myapp.jar /app/myapp.jar
# Start the application
CMD ["java", "-jar", "/app/myapp.jar"]
8.3 Effect Comparison
| Image Scheme | Size |
|---|---|
| openjdk:17-jdk | ~ 300 MB |
| jlink custom runtime | ~ 50-70 MB (depends on how many modules are included) |
By using <span>jlink</span>, the final Docker image size is significantly reduced, making it more suitable for cloud-native environments and rapid distribution.
9. Conclusion
- jlink + Docker is a golden combination for production environments.
- It makes Java images smaller and faster, especially suitable for microservices architecture.
- If combined with jpackage, it can even build cross-platform installable packages.