Startup: CDS, CRaC and Leyden
Purpose
Choose the startup mechanism whose granularity and deployment contract match the measured cost. CDS/AOT archives reuse selected class metadata, linked state and heap artifacts. JDK 25 AOT profiles can seed later compilation but do not archive native application code. CRaC preserves a warmed process image, including heap/JIT state subject to engine and resource policies, so it can remove much more work but requires a CRaC-enabled runtime, compatible Linux environment and an explicit lifecycle for external resources, time and identity.
The failures this prevents are all failures of premise: teaching -Xshare:dump before checking
the default archive in the deployed image; writing CRaC flags that a standard
Temurin or Oracle JDK 25 does not recognise at all; leaving -XX:AOTCacheOutput in the
production start command, where it retrains the cache on every launch; and quoting a speedup
percentage nobody measured.
Workflow
- Measure the real target. Record port-open/readiness, first successful representative response, first response meeting the latency SLO and time/requests to stable throughput. Improving class loading can affect several phases, but it does not prove warm-up is solved.
- Account for the actual runtime image. Mainline 64-bit JDK images commonly ship a default
CDS archive and
-Xshare:autois the default; customjlinkimages and vendor/platform builds may differ. Verify use with logs instead of inferring it from version alone. - Select by constraint, then by granularity. Use the decision tree in
references/technique-selection.md; the choice is decided by platform and build control long before it is decided by expected gain. - Use dynamic/AppCDS deliberately.
AutoCreateSharedArchiveis convenient for repeated local/CLI launches that exit cleanly and have a writable path; an immutable production image should normally build and validate its archive in CI rather than mutate it at shutdown. - Use the JDK 25 AOT cache when training/assembly can run against the exact deployable image. The JEP 514 training-output flag and the production-consumption flag are different.
- Make the training run representative. Profiles are only worth what the training exercised; refresh-and-exit can train startup and shared paths but does not exercise the request-specific paths or input distributions needed to establish endpoint warm-up.
- Build the archive and application image as one immutable, signed unit. Validation details changed in JDK 25 updates (including the JDK-8377932 fix); never use a historical weakness as the design. Pin the exact vendor/build and run a changed-JAR negative test in CI.
- Verify the cache is actually in use, then report the distribution with its protocol and the
phase it applies to—total startup or one phase. Use
-Xshare:on/AOTMode=onas CI compatibility gates; in production weigh fail-fast against crash-loop availability and expose fallback explicitly.
The working baseline here is HotSpot JDK 25; inspect compiler/toolchain, vendor update, resolved Spring/CRaC dependencies and final image before using a versioned recipe. Do not upgrade the project merely to match the examples. Return mechanism, lifecycle/artifact contract, exact build, training coverage, observed use/rejection and measured startup phases; label untested paths.
Rules
- CRaC is not mainline in OpenJDK 25.
-XX:CRaCCheckpointTo,-XX:CRaCRestoreFromandjcmd JDK.checkpointneed an explicitly CRaC-enabled build (Azul Zulu with CRaC, BellSoft Liberica with CRaC, or theopenjdk/cracfork); Temurin 25.0.3 rejects them withUnrecognized VM option 'CRaCCheckpointTo=…'andPrintFlagsFinal | grep -i cracprints nothing. Confirm that before writing a single CRaC flag. - JEP 483 (AOT class loading and linking) is Delivered in JDK 24 — not preview; it needs no
--enable-preview. JEP 514 (one-command ergonomics) and JEP 515 (AOT method profiling) are Delivered in JDK 25. JEP 516 (AOT object caching with any GC) is Delivered in JDK 26. On JDK 25, archived-heap support and cache compatibility depend on collector/build constraints; JEP 516 removes the any-GC restriction for AOT object caching in JDK 26, not every other cache compatibility constraint. - JEP status names the integration/release target, not proof that a particular vendor image, platform or deployed build implements it. Check release/GA availability separately. The Leyden project page still lists AOT native-code compilation as proposed work; JDK 25 profile caches must not be described as containing compiled application methods.
-XX:AOTCacheOutput=<file>requests training/assembly on each invocation and replaces output on success. The production start command—systemd unit, Docker ENTRYPOINT— must use-XX:AOTCache=<file>. These are different flags, not aliases.- JDK-8377932 allowed affected AOT-cache builds to accept a changed application JAR. The fix is recorded in JDK 25 update changelogs, so behavior cannot be inferred from feature version alone. Verify the vendor build's release notes and negative-test replacement of a JAR; regardless of the result, deploy cache and application artifacts atomically and never patch a live image in place.
-XX:+AutoCreateSharedArchivecreates/replaces a dynamic archive at normal VM exit under its documented conditions. Same-version classpath mismatch behavior has varied by update; do not call it self-healing. Use a build-id path and negative tests instead of relying on overwrite heuristics or concurrent production writers.- Under fallback modes (
-Xshare:auto,-XX:AOTMode=auto), an incompatible application archive may be skipped while the application continues. Turn rejection into a failed start with-Xshare:onor-XX:AOTMode=onwhere an unexpectedly cold JVM is worse than a crash-loop — verified: a compact-headers mismatch exits with status 1 underon. JDK_AOT_VM_OPTIONSconfigures the assembly child of the JEP 514 flow. Treat the overall command's exit status as insufficient evidence: assert a fresh non-empty output, inspect the assembly log and consume it once withAOTMode=on. Respect the exact JEP/runtime restrictions on classpath/module-path changes and unsupported inputs. Account for both training and assembly heaps plus native overhead during the one-command flow; the assembly child can inherit heap sizing and the same container resource envelope.- A custom
jlinkimage does not automatically prove a usable generated CDS archive. Usejlink --generate-cds-archivewhen supported/desired, inspect-Xlog:cds, and measure the size and startup trade-off. Do not attribute a result to module stripping or CDS without a controlled comparison;java -versiontext is a hint, not an acceptance test. - Traditional CDS does not archive native application code or trained method profiles; it reuses selected class metadata/heap artifacts and can reduce startup and multi-process footprint. Keep its effect distinct from JEP 515 profile seeding.
- JEP 515 persists execution profiles, not compiled code. The target still compiles; it just does not restart branch and type statistics from zero.
- Every CRaC-held external resource needs an owned lifecycle, whether supplied by framework
integration,
org.crac.Resource, or an engine policy. Database/messaging/HTTP connections, files, native state, TTL caches and timers need explicit checkpoint semantics. Restore may land on another host, so reconnect with current DNS, credentials, identity and clock assumptions. spring-boot:build-imageis a packaging mechanism, not proof of a CRaC-enabled runtime or checkpoint. Inspect the selected buildpack/JRE and resulting flags; checkpoint/restore also needs engine-specific Linux capabilities/policies at runtime. These inputs evolve independently.- On AWS Lambda the supported mechanism is SnapStart with
org.crachooks — the platform performs the Firecracker checkpoint and restore. Invokingjcmd JDK.checkpointor-XX:CRaCRestoreFrominside the function runtime is not a supported path. - A CRaC image and restore path depend on process memory, dirty/resident pages, engine, storage, compression and lazy-page strategy—not class count alone. Image transfer and page faults are workload/platform costs, not constants.
- Always state whether a percentage covers total startup or one phase. The two are routinely swapped, and the swap is what makes the claim unfalsifiable.
- Treat
.jsa,.aotand CRaC images as sensitive executable-derived artifacts. Pin provenance, restrict write/read access, scan/sign them with the application image, and assume a CRaC image contains every secret observed before checkpoint. Rotate credentials after restore when the provider contract requires freshness.
References
- Technique selection — the granularity table showing what each mechanism preserves and what it does not, the constraint-first decision tree, the JEP status timeline, measurement contract and AOT-coverage rules. Read before choosing a mechanism or defending the choice.
- Flags and workflows — the flag reference per technique, the AutoCreateSharedArchive and Leyden training flows end to end with the log lines each prints, the Spring training-run recipe, the verification commands that prove an archive or cache is in use, the CRaC resource lifecycle and container deployment gate. Read when writing start commands, a Dockerfile, a CI step or a deployment manifest.
- Validation and troubleshooting — what each
artefact checks at startup (JDK, flags, classpath, JAR size and mtime, module path), what
happens under
autoversuson, when the archive is regenerated, and the symptom table. Read when an archive or cache falls back, when old code runs after a deploy, or before deciding where the archive is built in the pipeline.