Subsystem · jlink

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 PATHjlink and jdeps ship with every JDK distribution, so there is nothing extra to install:

shell
$ 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:

Curie.toml
[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:

$ curie jlink --project my-cli
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/:

Run it directly — only the bundled runtime is needed, no system JDK:

$ ./target/runtime/bin/my-cli
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:

$ curie build --no-jlink
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:

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.