jlink runtime image
curie jlink assembles a self-contained JDK runtime image using the plain JDK's own jlink tool — no GraalVM install, no AOT compilation. It sits between a fat JAR and full GraalVM native: still a JVM (so no reachability-metadata tuning), but bundled with a minimal custom runtime so the target machine only needs bin/<app>, not a system-wide JDK.
Prerequisites
Any JDK 21+ already on PATH — jlink and jdeps ship with every JDK distribution, so there is nothing extra to install:
$ jlink --version 25.0.3 $ jdeps --version jdeps 25.0.3
Configuration
Add a [jlink] table. An empty table is enough to enable the step — all keys are optional:
[application] name = "my-cli" version = "0.1.0" mainClass = "com.example.Main" [jlink] # JDK modules to link in (default: auto-detected via jdeps) modules = ["java.base"] # Strip debug information from the runtime image stripDebug = true # Compress runtime image resources compress = true # Name of the launcher script (default: application name) outputName = "my-cli"
When modules is omitted, Curie runs jdeps --print-module-deps against the built JAR (and its dependency JARs) to detect the minimal set of JDK modules the app actually needs.
Building
curie jlink runs the full build pipeline (compile, test, package JAR) and then invokes jlink:
Building my-cli v0.1.0 Resolve deps 3 JAR(s) Compile 4 source file(s) Test 12 passed Package my-cli-0.1.0.jar jlink java.base,java.logging -> target/runtime/jdk Done target/runtime/bin/my-cli
The runtime image lands under target/runtime/:
runtime/jdk/— the custom JDK runtime built byjlinkruntime/lib/— the app JAR, plus alibs/subdirectory of dependency JARsruntime/bin/<name>/bin/<name>.bat— launcher scripts
Run it directly — only the bundled runtime is needed, no system JDK:
Hello from a jlink runtime image!
Integration with curie build
When [jlink] is present in Curie.toml, curie build also assembles the runtime image after packaging the JAR. Pass --no-jlink to skip it when iterating quickly:
Building my-cli v0.1.0 … (jlink step skipped) Done target/my-cli-0.1.0.jar
jlink vs. GraalVM native
Both produce a self-contained artifact, but they trade off differently:
- jlink — still a JVM, so no AOT-compatibility tuning (reflection, dynamic proxies, and resources all work exactly as they do today). Larger output and a normal JVM startup time, but no GraalVM install and nothing to configure beyond
[jlink]. - native — a true native binary: instant startup, minimal footprint, but requires GraalVM and often reachability metadata for reflection-heavy frameworks.
Docker integration
When [docker] is also enabled, the generated Dockerfile packages the whole target/runtime/ tree and runs the launcher — no separate JRE base image, since the runtime image already bundles its own JDK. See Docker + native/jlink.