JLink for Custom Runtime Creation in Java

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

FeatureBenefitExample
Smaller SizeReduced deployment size40MB vs 200MB+
Faster StartupOptimized class loading2x faster startup
Improved SecurityReduced attack surfaceOnly required modules
Self-containedNo external JDK neededSingle deployment unit
Custom LaunchersSimplified executionCustom command names

JLink enables creating highly optimized, minimal Java runtimes tailored to specific applications, significantly improving deployment efficiency and runtime performance.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper