On this page
jlink & jpackage
jlink (Java 9+) creates custom runtime images containing only the modules your application needs. jpackage (Java 14+) packages applications into native installers.
jlink — Custom Runtime Images
Reduce deployment size by including only required JDK modules instead of the full JDK (~300 MB → ~40 MB).
Basic Usage
# Compile modular application
javac -d out --module-source-path src $(find src -name "*.java")
# Create custom runtime
jlink --module-path $JAVA_HOME/jmods:out \
--add-modules com.example.myapp \
--launcher myapp=com.example.myapp/com.example.Main \
--output myapp-runtime \
--strip-debug \
--compress=2 \
--no-header-files \
--no-man-pages
Run:
myapp-runtime/bin/myapp
Finding Required Modules
# Analyze dependencies
jdeps --print-module-deps --ignore-not-found --multi-release 21 myapp.jar
# Output: java.base,java.logging,java.sql,java.naming,...
jlink --module-path $JAVA_HOME/jmods:myapp.jar \
--add-modules $(jdeps --print-module-deps myapp.jar) \
--output myapp-runtime
jlink Options
| Option | Purpose |
|---|---|
--strip-debug |
Remove debug info — smaller image |
--compress=2 |
Compress resources (0=none, 2=zip) |
--no-header-files |
Exclude C header files |
--no-man-pages |
Exclude man pages |
--launcher name=module/class |
Add executable launcher |
--vm server|client|minimal |
JVM variant |
jpackage — Native Installers
Creates platform-native packages (.msi, .dmg, .deb, .rpm):
Requirements
- Custom runtime from jlink (or full JDK)
- Application JAR or modular app
Basic Usage
jpackage --input lib \
--name MyApp \
--main-jar myapp.jar \
--main-class com.example.Main \
--runtime-image myapp-runtime \
--type dmg \
--app-version 1.0.0 \
--vendor "Example Inc" \
--description "My Java Application"
Output by Platform
| Platform | Format | Flag |
|---|---|---|
| macOS | .dmg or .pkg |
--type dmg |
| Windows | .msi or .exe |
--type msi |
| Linux | .deb or .rpm |
--type deb |
With Custom Icon
jpackage --input lib \
--name MyApp \
--main-jar myapp.jar \
--runtime-image myapp-runtime \
--icon myicon.icns \
--type dmg
End-to-End Example
# 1. Build JAR
./mvnw package -DskipTests
# 2. Determine modules
DEPS=$(jdeps --print-module-deps target/myapp.jar)
# 3. Create custom runtime
jlink --module-path $JAVA_HOME/jmods:target/myapp.jar \
--add-modules $DEPS \
--strip-debug --compress=2 \
--no-header-files --no-man-pages \
--launcher myapp=org.springframework.boot.loader.JarLauncher \
--output target/runtime
# 4. Create installer
jpackage --input target \
--name MyApp \
--main-jar myapp.jar \
--runtime-image target/runtime \
--type dmg \
--app-version 1.0.0
GraalVM Native Image (Alternative)
For even smaller, faster-starting deployments, GraalVM Native Image compiles to a standalone native binary:
native-image -jar myapp.jar myapp
./myapp # no JVM needed
Trade-offs: longer build time, reflection configuration required, no dynamic class loading.
Comparison
| Approach | Size | Startup | Dynamic Features |
|---|---|---|---|
| Full JDK + JAR | ~300 MB | Slow | Full |
| jlink runtime | ~40-80 MB | Medium | Full |
| GraalVM native | ~50-100 MB | Fast | Limited |
Best Practices
- Always use jlink for production deployments — avoid shipping the full JDK
- Run
jdepsto determine exact module dependencies - Test the custom runtime thoroughly — missing modules cause
NoClassDefFoundError - Use jpackage for user-facing desktop applications
- Consider GraalVM Native Image for CLI tools and serverless where startup time matters