# 6. The JDK 25 AOT cache [← 5. jlink](05-jlink.md) · [Index](../README.md) · Next: [7. PID 1 and signals →](07-pid1-and-signals.md) JDK 24 added the AOT cache (JEP 483: classes loaded and linked ahead of time), and JDK 25 made it one step (JEP 514, `-XX:AOTCacheOutput`) and added method profiles to it (JEP 515). Spring Boot's documented Dockerfile does a training run at image build time: ```dockerfile RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"] ``` `spring.context.exit=onRefresh` starts the context and exits, so the training run needs no database or network - unless your beans touch them during startup. ## What it buys and what it costs | | `layered-jre` | `aot-cache` | |---|---|---| | Started in (median of 3) | 3.356 s | **1.638 s** | | on disk | 377 MB | 440 MB (+63 MB) | | pushed after a one-line change | 6 KB | **15.3 MB** | The last row is the one nobody mentions. The training run happens *after* the application layer is copied, so every code change invalidates it and the cache layer is rebuilt and re-pushed. Half the startup time costs back most of what layering saved on pushes. Whether that trade is worth it depends on how often pods start versus how often you deploy - scale-to-zero and aggressive autoscaling say yes; a fleet that deploys twenty times a day and rarely restarts says no. ## A cache from a different JVM The cache is only valid for the exact JVM build that wrote it. [`Dockerfile.aot-cache-mismatch`](../docker/Dockerfile.aot-cache-mismatch) copies the trained application onto the distroless image, whose JVM is Temurin 25.0.4.1 instead of 25.0.4 ([`aot-cache-mismatch.txt`](output/aot-cache-mismatch.txt)): ``` [0.007s][warning][aot] The AOT cache was created by a different version or build of HotSpot [0.007s][error ][aot] An error has occurred while processing the AOT cache. Run with -Xlog:aot for details. [0.008s][error ][aot] Loading static archive failed. [0.008s][error ][aot] Unable to map shared spaces Starting ImagesApplication v1.0.0 using Java 25.0.4.1 with PID 1 (/application/application.jar started by nonroot in /ap Started ImagesApplication in 3.564 seconds (process running for 4.05) ``` Four lines at `error` level, and then the application starts normally without the cache - back at 3.5 s. Nothing fails a health check. The way this happens in practice is a floating base-image tag (`25-jre`) moving to a new JVM build between the stage that trained the cache and a later rebuild that reused a cached training layer - or a multi-stage build that trains on one image and runs on another, as here. Train and run on the same image, pin it by digest, and alert on `[aot]` lines at `error`.