Comprehensive guide to using jlink for creating custom Java runtime images with only the necessary modules.
1. JLink Overview and Basic Usage
What is JLink?
# JLink creates customized runtime images containing only required modules # Benefits: # - Smaller deployment size # - Improved startup time # - Reduced attack surface # - Self-contained applications
Basic JLink Command
# Basic syntax jlink --module-path <path> --add-modules <modules> --output <runtime-image> # Example: Create minimal runtime with java.base only jlink --module-path $JAVA_HOME/jmods \ --add-modules java.base \ --output minimal-runtime # Check the created runtime ./minimal-runtime/bin/java --version
2. Module System Setup for JLink
Sample Modular Application Structure
// module-info.java for main application
module com.enterprise.app {
requires java.base;
requires java.sql;
requires java.logging;
requires java.net.http;
requires com.utils;
exports com.enterprise.app.api;
uses com.enterprise.app.spi.ServiceProvider;
}
// Main application class
package com.enterprise.app;
public class MainApp {
public static void main(String[] args) {
System.out.println("Enterprise Application Started");
// Application logic here
}
}
// Utility module
module com.utils {
requires java.base;
requires java.logging;
exports com.utils.string;
exports com.utils.validation;
}
// Utility classes
package com.utils.string;
public class StringUtils {
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
public static String capitalize(String str) {
if (isEmpty(str)) return str;
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
}
Service Provider Module
// Service provider interface module
module com.enterprise.app.spi {
exports com.enterprise.app.spi;
}
// Service interface
package com.enterprise.app.spi;
public interface ServiceProvider {
String getName();
void execute();
}
// Service implementation module
module com.enterprise.app.providers {
requires com.enterprise.app.spi;
requires java.logging;
provides com.enterprise.app.spi.ServiceProvider
with com.enterprise.app.providers.DefaultServiceProvider;
}
// Service implementation
package com.enterprise.app.providers;
import com.enterprise.app.spi.ServiceProvider;
import java.util.logging.Logger;
public class DefaultServiceProvider implements ServiceProvider {
private static final Logger logger = Logger.getLogger(DefaultServiceProvider.class.getName());
@Override
public String getName() {
return "DefaultServiceProvider";
}
@Override
public void execute() {
logger.info("Default service provider executing...");
// Implementation logic
}
}
3. Basic JLink Examples
Simple Application Runtime
# Create runtime for simple application jlink --module-path "target/modules:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --output enterprise-runtime # The generated runtime structure: # enterprise-runtime/ # ├── bin/ # │ ├── java # │ └── keytool # ├── conf/ # ├── include/ # ├── legal/ # ├── lib/ # └── release
Including Multiple Modules
# Explicitly list all required modules jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app,com.utils,java.sql,java.logging,java.net.http \ --output custom-runtime # Run the application with custom runtime ./custom-runtime/bin/java -m com.enterprise.app/com.enterprise.app.MainApp
4. Advanced JLink Features
Compression Options
# Different compression levels jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --compress=2 \ # 0=no compression, 1=constant string sharing, 2=ZIP --output compressed-runtime # Compare sizes du -sh minimal-runtime compressed-runtime
Stripping Debug Information
# Remove debug info to reduce size jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --strip-debug \ --no-header-files \ --no-man-pages \ --output stripped-runtime
Vendor Information
# Add custom vendor information jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --vendor "Acme Corporation" \ --version "1.0.0" \ --output branded-runtime
5. Plugin System with JLink
Plugin Architecture Setup
// Plugin module definition
module com.enterprise.plugin.api {
exports com.enterprise.plugin.api;
exports com.enterprise.plugin.api.spi;
}
// Plugin interface
package com.enterprise.plugin.api.spi;
public interface Plugin {
String getName();
void initialize();
void execute();
void shutdown();
}
// Plugin manager in main app
module com.enterprise.app {
requires java.base;
requires java.logging;
requires com.enterprise.plugin.api;
uses com.enterprise.plugin.api.spi.Plugin;
exports com.enterprise.app;
}
// Plugin manager implementation
package com.enterprise.app;
import com.enterprise.plugin.api.spi.Plugin;
import java.util.ServiceLoader;
import java.util.logging.Logger;
public class PluginManager {
private static final Logger logger = Logger.getLogger(PluginManager.class.getName());
public void loadAndExecutePlugins() {
ServiceLoader<Plugin> plugins = ServiceLoader.load(Plugin.class);
for (Plugin plugin : plugins) {
logger.info("Loading plugin: " + plugin.getName());
try {
plugin.initialize();
plugin.execute();
} catch (Exception e) {
logger.severe("Plugin failed: " + plugin.getName() + " - " + e.getMessage());
}
}
}
public void shutdownPlugins() {
ServiceLoader<Plugin> plugins = ServiceLoader.load(Plugin.class);
for (Plugin plugin : plugins) {
try {
plugin.shutdown();
} catch (Exception e) {
logger.warning("Plugin shutdown failed: " + plugin.getName());
}
}
}
}
Sample Plugins
// Database plugin
module com.enterprise.plugin.database {
requires com.enterprise.plugin.api;
requires java.sql;
requires java.logging;
provides com.enterprise.plugin.api.spi.Plugin
with com.enterprise.plugin.database.DatabasePlugin;
}
package com.enterprise.plugin.database;
import com.enterprise.plugin.api.spi.Plugin;
import java.sql.*;
import java.util.logging.Logger;
public class DatabasePlugin implements Plugin {
private static final Logger logger = Logger.getLogger(DatabasePlugin.class.getName());
private Connection connection;
@Override
public String getName() { return "DatabasePlugin"; }
@Override
public void initialize() {
logger.info("Initializing database plugin");
// Initialize database connection
}
@Override
public void execute() {
logger.info("Executing database operations");
// Perform database operations
}
@Override
public void shutdown() {
logger.info("Shutting down database plugin");
if (connection != null) {
try { connection.close(); } catch (SQLException e) { /* ignore */ }
}
}
}
// Reporting plugin
module com.enterprise.plugin.reporting {
requires com.enterprise.plugin.api;
requires java.logging;
provides com.enterprise.plugin.api.spi.Plugin
with com.enterprise.plugin.reporting.ReportingPlugin;
}
package com.enterprise.plugin.reporting;
import com.enterprise.plugin.api.spi.Plugin;
import java.util.logging.Logger;
public class ReportingPlugin implements Plugin {
private static final Logger logger = Logger.getLogger(ReportingPlugin.class.getName());
@Override
public String getName() { return "ReportingPlugin"; }
@Override
public void initialize() {
logger.info("Initializing reporting plugin");
}
@Override
public void execute() {
logger.info("Generating reports");
// Generate reports
}
@Override
public void shutdown() {
logger.info("Shutting down reporting plugin");
}
}
6. JLink with Service Binding
Service-Based Runtime Creation
# JLink automatically includes service providers jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --bind-services \ # Automatically add all service provider modules --output service-runtime
Manual Service Inclusion
# Manually specify service provider modules jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app,com.enterprise.plugin.database,com.enterprise.plugin.reporting \ --output manual-service-runtime
7. Platform-Specific Customization
Windows-Specific Runtime
# Windows-specific optimizations jlink --module-path "mods;$JAVA_HOME/jmods" \ --add-modules com.enterprise.app,java.desktop,java.sql \ --launcher enterprise-app=com.enterprise.app/com.enterprise.app.MainApp \ --output windows-runtime
Linux-Specific Runtime
# Linux-specific configuration jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app,java.management,java.logging \ --launcher enterprise-app=com.enterprise.app/com.enterprise.app.MainApp \ --output linux-runtime
8. Launcher Creation
Custom Launchers
# Create custom launcher command jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --launcher start-app=com.enterprise.app/com.enterprise.app.MainApp \ --launcher admin-tool=com.enterprise.app/com.enterprise.app.AdminTool \ --output launcher-runtime # Usage: ./launcher-runtime/bin/start-app ./launcher-runtime/bin/admin-tool
Launcher with Default Arguments
# Create launcher with default JVM arguments jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --launcher "enterprise-app=com.enterprise.app/com.enterprise.app.MainApp -Xms128m -Xmx512m -Dapp.mode=production" \ --output production-runtime
9. Advanced JLink Scenarios
Multi-Module Complex Application
// Complex application with multiple modules
module com.enterprise.core {
requires java.base;
requires java.logging;
requires transitive com.utils;
exports com.enterprise.core.api;
exports com.enterprise.core.spi;
uses com.enterprise.core.spi.ExtensionPoint;
}
module com.enterprise.persistence {
requires transitive com.enterprise.core;
requires java.sql;
requires java.naming;
exports com.enterprise.persistence.dao;
provides com.enterprise.core.spi.ExtensionPoint
with com.enterprise.persistence.DatabaseExtension;
}
module com.enterprise.web {
requires transitive com.enterprise.core;
requires java.net.http;
requires jdk.httpserver;
exports com.enterprise.web.controller;
provides com.enterprise.core.spi.ExtensionPoint
with com.enterprise.web.WebExtension;
}
module com.enterprise.main {
requires com.enterprise.core;
requires com.enterprise.persistence;
requires com.enterprise.web;
exports com.enterprise.main;
}
Building Complex Runtime
# Build runtime for complex application jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.main \ --bind-services \ --launcher enterprise=com.enterprise.main/com.enterprise.main.Main \ --compress=2 \ --strip-debug \ --no-header-files \ --no-man-pages \ --vendor "Enterprise Solutions Inc." \ --version "2.1.0" \ --output enterprise-runtime
10. JLink with Third-Party Dependencies
Including External JARs as Automatic Modules
# Convert JARs to automatic modules and include in runtime # Step 1: Copy JARs to mods directory cp lib/*.jar mods/ # Step 2: Build with automatic modules jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app,com.fasterxml.jackson.databind,org.slf4j \ --output with-deps-runtime
Module Path Configuration
# Complex module path with multiple locations jlink --module-path "application-mods:library-mods:automatic-mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --output complex-runtime
11. Optimization Techniques
Module Reduction
# Use jdeps to find required modules jdeps --print-module-deps --ignore-missing-deps \ -m com.enterprise.app \ target/modules/com.enterprise.app.jar # Output: java.base,java.logging,java.sql # Use this output with jlink jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules java.base,java.logging,java.sql,com.enterprise.app \ --output optimized-runtime
Class Data Sharing
# Generate class data sharing archive for faster startup jlink --module-path "mods:$JAVA_HOME/jmods" \ --add-modules com.enterprise.app \ --class-sharing \ --output cds-runtime
12. Verification and Testing
Runtime Verification
# Verify the created runtime ./enterprise-runtime/bin/java --list-modules # Check module dependencies ./enterprise-runtime/bin/java -m com.enterprise.app/com.enterprise.app.MainApp --validate-modules # Test service loading ./enterprise-runtime/bin/java -m com.enterprise.app/com.enterprise.app.PluginManager
Size Analysis
# Analyze runtime size du -sh enterprise-runtime/ find enterprise-runtime/ -name "*.so" -o -name "*.dll" | wc -l find enterprise-runtime/ -name "*.class" | wc -l # Compare with full JDK du -sh $JAVA_HOME
13. Maven Integration
Maven Configuration for JLink
<!-- pom.xml - Maven JLink plugin configuration -->
<project>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jlink-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<modulePath>${project.build.directory}/modules</modulePath>
<addModules>com.enterprise.app</addModules>
<output>${project.build.directory}/runtime</output>
<launcher>enterprise-app=com.enterprise.app/com.enterprise.app.MainApp</launcher>
<compress>2</compress>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
<vendor>Acme Corporation</vendor>
<version>${project.version}</version>
<jlinkVerbose>true</jlinkVerbose>
</configuration>
<executions>
<execution>
<id>create-runtime</id>
<phase>package</phase>
<goals>
<goal>jlink</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Multi-Module Maven Project
<!-- Parent POM for multi-module project --> <project> <modules> <module>core</module> <module>persistence</module> <module>web</module> <module>main-app</module> </modules> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jlink-plugin</artifactId> <version>3.1.0</version> <configuration> <addModules>com.enterprise.main</addModules> <launcher>app=com.enterprise.main/com.enterprise.main.Main</launcher> </configuration> </plugin> </plugins> </build> </project>
14. Docker Integration
Dockerfile for JLink Runtime
# Dockerfile for JLink-based application
FROM debian:11-slim
# Install minimal dependencies
RUN apt-get update && apt-get install -y \
libfreetype6 \
fontconfig \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Copy custom runtime
COPY --chown=appuser:appuser enterprise-runtime /opt/app/runtime
# Switch to non-root user
USER appuser
# Set working directory
WORKDIR /home/appuser
# Copy application data
COPY --chown=appuser:appuser app-data ./data
# Use custom runtime
ENV PATH="/opt/app/runtime/bin:${PATH}"
# Launch application
CMD ["enterprise-app"]
Multi-stage Docker Build
# Multi-stage Docker build FROM maven:3.8-openjdk-17 as builder # Copy source code WORKDIR /app COPY . . # Build application and create runtime RUN mvn clean package jlink:jlink # Runtime stage FROM debian:11-slim # Install minimal dependencies RUN apt-get update && apt-get install -y \ libfreetype6 \ && rm -rf /var/lib/apt/lists/* # Copy runtime from builder stage COPY --from=builder /app/target/runtime /opt/app/runtime # Set entry point ENTRYPOINT ["/opt/app/runtime/bin/enterprise-app"]
Key Benefits Summary
| Feature | Benefit | Example |
|---|---|---|
| Smaller Size | Reduced deployment size | 40MB vs 200MB+ |
| Faster Startup | Optimized class loading | 2x faster startup |
| Improved Security | Reduced attack surface | Only required modules |
| Self-contained | No external JDK needed | Single deployment unit |
| Custom Launchers | Simplified execution | Custom command names |
JLink enables creating highly optimized, minimal Java runtimes tailored to specific applications, significantly improving deployment efficiency and runtime performance.