skip to content

How does `java -Djarmode=layertools extract` work, and how do you wire it into a multi-stage Dockerfile?

level: seniorimportance: must knowfreq 50%

answer

  1. -Djarmode=layertools (tools in 3.3+)
  2. extract -> one dir per layer
  3. builder stage extracts, runtime COPYs each
  4. COPY order stable -> volatile
  5. start with JarLauncher, not java -jar

basics

~20 s

java -Djarmode=layertools -jar app.jar extract reads layers.idx and writes one folder per layer. In a builder Docker stage you run it, then COPY each folder into the final image separately (stable first) and start with JarLauncher.

solid answer

~40 s

`jarmode` is a special launch mode: instead of running the app's `main`, the jar runs a built-in tool. With `-Djarmode=layertools` (older) or `-Djarmode=tools` (Boot 3.3+), the `extract` command reads `BOOT-INF/layers.idx` and unpacks the jar into one directory per layer — `dependencies/`, `spring-boot-loader/`, `snapshot-dependencies/`, `application/` — each a runnable slice. `list` prints the layer names. In a multi-stage Dockerfile, a `builder` stage copies the jar and runs the extract; the final runtime stage does `COPY --from=builder` for each layer directory in stable-to-volatile order, so each becomes its own cached image layer. Because you extracted rather than kept a fat jar, you start the app with the launcher — `ENTRYPOINT ["java","org.springframework.boot.loader.launch.JarLauncher"]` — not `java -jar`. The launcher rebuilds the classpath from the extracted directories. This yields fast, cache-friendly rebuilds where only the tiny application layer changes.

code

java · 17 lines
java
// Docker build config (java-fenced for readability) — multi-stage layered extraction.
//
// FROM eclipse-temurin:21-jre AS builder
// WORKDIR /builder
// COPY build/libs/*.jar app.jar
// RUN java -Djarmode=layertools -jar app.jar extract --destination extracted
//
// FROM eclipse-temurin:21-jre
// WORKDIR /app
// COPY --from=builder /builder/extracted/dependencies/ ./
// COPY --from=builder /builder/extracted/spring-boot-loader/ ./
// COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
// COPY --from=builder /builder/extracted/application/ ./
// # Boot 3.2+ launcher package:
// ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
//
// To just inspect layers:  java -Djarmode=layertools -jar app.jar list

go deeper

for a junior

Know that a command unpacks the jar into layer folders for Docker; details optional.

for a middle

Explain the builder/runtime multi-stage split and stable-first COPY order.

for a senior

Fluent on jarmode mechanics, list vs extract, JarLauncher entrypoint, and the launcher package change in 3.2+.

for a principal

Weigh layertools-Dockerfile vs buildpacks vs CDS/AOT; own the org-wide base-image and CI cache strategy.

### What `jarmode` is A Spring Boot executable jar normally launches your `main` via its `Main-Class` manifest entry. The **`jarmode`** system property hijacks that: `java -Djarmode=<mode> -jar app.jar <args>` boots the jar into an alternate built-in program instead of your application. The mode for layer handling is historically **`layertools`**; **Spring Boot 3.3+** renamed/expanded it to **`tools`** (which also does CDS training runs and jar extraction), while keeping `layertools` working. Assume `layertools` unless told otherwise. ### `layertools` commands - **`list`** — prints the layer names from `BOOT-INF/layers.idx`, most-stable first. - **`extract`** — reads `layers.idx` and writes **one directory per layer** into the current working directory (override with `--destination <dir>`). Each directory holds the files belonging to that layer, laid out so the set of directories together form a runnable application. - (`help` prints usage.) So `java -Djarmode=layertools -jar app.jar extract` produces `./dependencies`, `./spring-boot-loader`, `./snapshot-dependencies`, `./application`. ### Multi-stage Dockerfile pattern ```dockerfile # --- builder stage: unpack the jar into layers --- FROM eclipse-temurin:21-jre AS builder WORKDIR /builder ARG JAR_FILE=target/*.jar COPY ${JAR_FILE} app.jar RUN java -Djarmode=layertools -jar app.jar extract --destination extracted # --- runtime stage: copy each layer as its own image layer --- FROM eclipse-temurin:21-jre WORKDIR /app COPY --from=builder /builder/extracted/dependencies/ ./ COPY --from=builder /builder/extracted/spring-boot-loader/ ./ COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ COPY --from=builder /builder/extracted/application/ ./ ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"] ``` Key points: - **Order of COPYs = stable→volatile.** Docker invalidates a layer and everything after it; put `dependencies` first, `application` last so a code change only rebuilds the last, tiny layer. - Each `COPY` becomes a distinct **image layer**, mirroring the jar layers. - The final image does **not** contain a fat jar — it contains the *extracted* directory tree. ### Why `JarLauncher` and not `java -jar` After extraction there's no single executable jar to `java -jar`. The extracted layout is designed to be launched by **`org.springframework.boot.loader.launch.JarLauncher`** (in Boot 3.2+; earlier it was `org.springframework.boot.loader.JarLauncher`). The launcher discovers the extracted `BOOT-INF/classes` and `BOOT-INF/lib` locations, assembles the classpath in the correct order, finds your `Start-Class`, and invokes it. Using the wrong package name for the launcher (2.x vs 3.2+) is a classic runtime `ClassNotFoundException`. ### Edge cases / gotchas - **Boot 3.3+ naming:** prefer `-Djarmode=tools ... extract` on new versions; `layertools` still works for compatibility. Don't mix up the launcher package between major versions. - **`--destination`** keeps extraction tidy; without it, layers land in CWD and can clutter the build context. - **The jar must be layered** (default 2.4+). If someone set `layered { enabled = false }`, `layers.idx` is absent and extraction produces a single blob. - **Buildpacks alternative:** `./gradlew bootBuildImage` produces an optimized, layered OCI image with no Dockerfile at all — often preferable unless you need Dockerfile control. - **Working directory / entrypoint:** run the launcher from the directory the layers were copied into; ensure `WORKDIR` matches. - Extraction is a **build-time** step only; it doesn't change app behavior, config, or the Spring context.

  • Why start the container with JarLauncher instead of `java -jar app.jar`?
    After extraction there is no fat jar to run — only the exploded layer directories. JarLauncher reconstructs the classpath from BOOT-INF/classes and BOOT-INF/lib and invokes your Start-Class. `java -jar` needs a single executable archive that no longer exists.
  • What changed about jarmode in Spring Boot 3.3?
    The `tools` jarmode was introduced (superseding/augmenting `layertools`), adding CDS training and extraction options; `layertools` still works. Also, the launcher class moved to org.springframework.boot.loader.launch.JarLauncher in 3.2+.
  • How do you verify the layers before writing the Dockerfile?
    Run `java -Djarmode=layertools -jar app.jar list` — it prints the layer names in order from layers.idx, confirming the jar is layered and showing the exact directory names to COPY.

saying these in an interview costs you the question

  • Starting the extracted image with `java -jar` (no jar exists after extraction)
  • Using the 2.x launcher package org.springframework.boot.loader.JarLauncher on Boot 3.2+
  • Copying the application layer before the dependency layers
  • Thinking jarmode runs the app's main — it runs a built-in tool instead

context