How does Spring Boot's training-run archive work, and what does -Dspring.context.exit=onRefresh do?
answer
- onRefresh: refresh context then exit before serving
- ArchiveClassesAtExit captures the training-run classes
- Extract jar first for a stable classpath
- BP_JVM_CDS_ENABLED=true = buildpack does it at build time
- CDS + AOT stack on the JVM
basics
~10 s-Dspring.context.exit=onRefresh tells Spring Boot to start the context, finish refreshing (all beans created, classes loaded), then exit the JVM before serving traffic. Run it with -XX:ArchiveClassesAtExit to capture those classes into a CDS archive.
solid answer
~40 sSpring Boot 3.3+ formalizes the CDS 'training run'. The property `spring.context.exit=onRefresh` makes the application context proceed through full refresh — instantiating all non-lazy singletons, resolving autoconfiguration, loading the classes required to bootstrap — and then the JVM exits cleanly instead of blocking on the web server. Pair it with `-XX:ArchiveClassesAtExit=application.jsa` and the exit dumps exactly those startup classes into a dynamic CDS archive. In production you then run with `-XX:SharedArchiveFile=application.jsa`. Best practice is to run against the extracted jar layout (`java -Djarmode=tools -jar app.jar extract`) so the classpath is stable and matches production. The recommended zero-effort path is the Paketo/Spring Boot buildpack with `BP_JVM_CDS_ENABLED=true`, which performs the training run and bakes the archive into the image at build time. CDS composes with Spring AOT for additional startup gains.
code
java · 19 lines// Gradle build-image with buildpack-automated CDS training run:
// ./gradlew bootBuildImage \
// --imageName=example/app \
// -Dorg.gradle.jvmargs=... \
// -PBP_JVM_CDS_ENABLED=true // (usually set as a build env var)
//
// Or set the buildpack env directly:
// BP_JVM_CDS_ENABLED=true ./mvnw spring-boot:build-image
//
// Beware: onRefresh actually instantiates eager singletons, so a bean
// like this runs its @PostConstruct during the TRAINING run too:
@org.springframework.stereotype.Component
class Warmup {
@jakarta.annotation.PostConstruct
void init() {
// Runs at context refresh -> also executes during the CDS training run.
// Avoid real external side effects here, or guard the training profile.
}
}go deeper
Know onRefresh means 'start up then quit', used to make the archive.
Explain that it refreshes the context and exits before serving, paired with ArchiveClassesAtExit.
Automates via buildpack, uses the extracted layout, and knows onRefresh actually runs bean init (side-effect risk).
Designs the CI pipeline (profile-isolated training run, archive baked into image, re-trained on dependency/JDK change) and combines CDS with AOT.
## Why a 'training run' at all Dynamic CDS archives the classes **actually loaded during a run**. To get a useful Spring Boot archive you must load the same classes production will need at startup — ideally without the noise and blocking of actually serving traffic. That controlled run is the **training run**. ## `spring.context.exit=onRefresh` This Spring Boot property (available from **Spring Boot 3.3**) changes the lifecycle: the `ApplicationContext` is created and **refreshed** — meaning autoconfiguration is evaluated, all eager singleton beans are instantiated, and the classes behind them are loaded — and then, instead of the normal 'keep running / bind the web server' step, the JVM **exits**. Because it exits *cleanly*, `-XX:ArchiveClassesAtExit` fires and writes the archive. Contrast with a plain `java -jar app.jar` training run: an embedded-server app boots Tomcat/Netty and blocks forever, so the JVM never exits and no archive is produced. `onRefresh` solves that deterministically and quickly. (The related value used elsewhere is that the context reaches refresh but doesn't advance to the running phase.) ## The end-to-end flow ``` # 1. Extract the fat jar into a stable, exploded classpath java -Djarmode=tools -jar app.jar extract --destination app cd app # 2. Training run: full context init, then exit -> dump archive java -Dspring.context.exit=onRefresh \ -XX:ArchiveClassesAtExit=application.jsa \ -jar app.jar # 3. Production: reuse the archive on every start java -XX:SharedArchiveFile=application.jsa -jar app.jar ``` ## Why the extracted layout matters CDS wants a **stable classpath** that matches between training and production. Running from the extracted layout (plain directories of classes/jars) rather than relying on nested-jar classloading gives a deterministic classpath and lets CDS map the most classes. Spring Boot's `-Djarmode=tools ... extract` produces exactly this. ## Buildpack automation (recommended in practice) The **Paketo / Spring Boot buildpacks** support `BP_JVM_CDS_ENABLED=true`. When set, the buildpack performs the training run at **image build time** and embeds `application.jsa` in the resulting container image, wiring the `-XX:SharedArchiveFile` flag automatically. You get CDS with no manual scripting: `./mvnw spring-boot:build-image` (or Gradle `bootBuildImage`) with the env set. ## CDS + AOT together CDS and **Spring AOT** are complementary. AOT (`spring-boot-maven-plugin` `process-aot`, or `-Dspring.aot.enabled=true` in the AOT-processed jar) pre-computes bean definitions and generates source at build time; CDS caches the *class-loading* work. Used together on the JVM they stack for a bigger startup reduction than either alone — without the closed-world limits of a full native image. ## Gotchas - The training run must **exit cleanly**; a crash or kill yields no archive. - **Re-run the training step whenever dependencies, the app jar, or the JDK change** — otherwise the archive under-covers or is rejected. - `onRefresh` runs your bean initialization logic (constructors, `@PostConstruct`), so beans that do real I/O at construction will execute during the training run — make sure that's acceptable (e.g., avoid firing external side effects, or profile the training run so it doesn't hit prod resources). - CDS archives startup-time classes; endpoints first exercised only under real traffic aren't in the archive unless the training run touched them.
- Your training run hits a production database during context refresh. Why is that a concern and how do you avoid it?onRefresh instantiates eager singletons and runs @PostConstruct, so any I/O in bean init executes during training — potentially against real resources. Run the training step with a training/CI profile (mock or local datasources) or make side-effecting init lazy, so the archive is built without touching production.
- Can you use CDS and Spring AOT at the same time?Yes — they are complementary. AOT pre-generates bean definitions and source at build time; CDS caches class-loading work. On the JVM they stack for greater startup reduction, and unlike native image you keep full runtime dynamism.
- Why run against the extracted jar rather than the nested fat jar?CDS needs a stable classpath that matches production to map the most classes. The extracted (exploded) layout from java -Djarmode=tools -jar app.jar extract gives a deterministic file-based classpath instead of relying on Spring Boot's nested-jar classloader.
saying these in an interview costs you the question
- Thinking onRefresh skips bean creation (it doesn't — eager singletons run)
- Running the training step against production resources during context refresh
- Believing a normal blocking `java -jar` run can produce the archive
- Assuming CDS and AOT are mutually exclusive