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.