skip to content

Walk through how you enable Project CDS in a Spring Boot 3.3+ app end to end.

level: middleimportance: must knowfreq 30%

answer

  1. extract -> train -> run
  2. -Djarmode=tools extract --destination
  3. ArchiveClassesAtExit at training
  4. spring.context.exit=onRefresh to quit after refresh
  5. SharedArchiveFile at runtime

basics

~10 s

Three steps: (1) extract the fat jar to a folder, (2) do a training run with -XX:ArchiveClassesAtExit=app.jsa and -Dspring.context.exit=onRefresh to produce the archive, (3) run normally with -XX:SharedArchiveFile=app.jsa.

solid answer

~40 s

Spring Boot 3.3+ CDS is a three-step workflow. First, extract the executable jar so classes sit on a flat classpath: `java -Djarmode=tools -jar my-app.jar extract --destination app` — CDS can't archive classes from nested jars inside a fat jar. Second, run a training run against the extracted app to build the archive: `java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar app/my-app.jar`. `ArchiveClassesAtExit` tells the JVM to dump a dynamic AppCDS archive when it exits, and `spring.context.exit=onRefresh` makes Spring Boot close the context right after refresh so the run exercises class loading and then quits instead of staying up serving requests. Third, run in production pointing at the archive: `java -XX:SharedArchiveFile=application.jsa -jar app/my-app.jar`. If the archive is valid the JVM memory-maps it; if not, it silently falls back to normal loading.

code

java · 13 lines
java
// 1) Extract the executable jar onto a flat classpath
//    java -Djarmode=tools -jar my-app.jar extract --destination app
//
// 2) Training run: produce the AppCDS archive, then exit after refresh
//    java -XX:ArchiveClassesAtExit=application.jsa \
//         -Dspring.context.exit=onRefresh \
//         -jar app/my-app.jar
//
// 3) Production run: memory-map the archive
//    java -XX:SharedArchiveFile=application.jsa -jar app/my-app.jar
//
// Buildpack automation (bakes steps 1-2 into the image):
//    build with BP_JVM_CDS_ENABLED=true

go deeper

for a junior

Remember the three verbs: extract, train, run — and that a .jsa file is produced then reused.

for a middle

Reproduce the exact flags: ArchiveClassesAtExit + spring.context.exit=onRefresh for training, SharedArchiveFile for runtime, and know why the jar is extracted.

for a senior

Explain dynamic AppCDS (no class list needed), why onRefresh gives a clean exit, and how to bake the archive into an image.

for a principal

Own the CI/build-time strategy: buildpack automation, archive regeneration on dependency/JDK bumps, and treating the .jsa as a build artifact.

## The three phases Project CDS in Spring Boot 3.3+ is a **build-once, run-many** flow: extract, train, run. ### 1) Extract the jar A Spring Boot executable ('fat'/'uber') jar nests dependency jars **inside** it and uses a custom classloader. CDS archiving does not work against classes loaded from jars-within-a-jar; the classes need to be on a **normal, flat classpath**. Boot 3.3's `tools` jarmode extracts the app into a directory: ``` java -Djarmode=tools -jar my-app.jar extract --destination app ``` This produces `app/my-app.jar` plus an unpacked `app/lib/` of dependencies, referenced via the manifest `Class-Path`, so the JVM loads from real files. (Note: `-Djarmode=tools` is the Boot 3.3 replacement for the older `-Djarmode=layertools`.) ### 2) Training run — produce the archive ``` java -XX:ArchiveClassesAtExit=application.jsa \ -Dspring.context.exit=onRefresh \ -jar app/my-app.jar ``` - **`-XX:ArchiveClassesAtExit=application.jsa`** is JDK's *dynamic AppCDS*: the JVM records every class it loads during this run and, at JVM exit, writes them into `application.jsa`. No prior class list is needed. - **`-Dspring.context.exit=onRefresh`** is a Spring Boot property that closes the `ApplicationContext` immediately after it is refreshed. That means the training run performs the full bean-loading / auto-configuration class-loading path (which is what you want captured) and then exits cleanly, rather than binding the web port and running forever. Without it, the app would start up and stay running, and you'd have to kill it — and killing it may not trigger the archive dump cleanly. After this step you have `application.jsa`. ### 3) Production run — use the archive ``` java -XX:SharedArchiveFile=application.jsa -jar app/my-app.jar ``` **`-XX:SharedArchiveFile=application.jsa`** tells the JVM to memory-map that archive at startup and reuse the pre-parsed class metadata. The real app now boots noticeably faster. ## Automating it in an image You usually run steps 1-2 at **build time** and bake `application.jsa` into the container so production just runs step 3. Spring Boot's build plugins + Paketo buildpacks can do this automatically: set **`BP_JVM_CDS_ENABLED=true`** when building the image (e.g. via `spring-boot:build-image`) and the buildpack performs the training run and wires `-XX:SharedArchiveFile` for you. ## Gotchas - Forgetting to **extract** first: archiving against the fat jar gives little/no benefit. - Forgetting **`spring.context.exit=onRefresh`**: the training run won't exit, or you kill it and get a poor/empty archive. - The archive is coupled to the exact **classpath and JDK version** used to build it — regenerate it whenever dependencies or the JDK change.

  • Why can't you generate the archive directly from the Spring Boot fat jar?
    CDS archiving needs classes on a flat, real-file classpath. A fat jar nests dependency jars inside itself behind a custom classloader, which CDS can't archive effectively — so you extract with `-Djarmode=tools` first.
  • What does spring.context.exit=onRefresh do and why use it only for training?
    It closes the ApplicationContext right after refresh so the process exercises the class-loading path and then exits. You use it only for the training run; in production you omit it so the app actually stays up and serves.
  • How do you make this repeatable in CI/containers?
    Run extract + training at build time and bake the .jsa into the image, or let Paketo buildpacks do it via BP_JVM_CDS_ENABLED=true so production just runs with -XX:SharedArchiveFile.

saying these in an interview costs you the question

  • Using -XX:SharedArchiveFile during the training run (that reads an archive; ArchiveClassesAtExit writes one).
  • Skipping extraction and archiving straight from the fat jar.
  • Leaving spring.context.exit=onRefresh set in production so the app exits right after startup.
  • Believing one archive works forever regardless of dependency/JDK changes.

context