Can JVM caches speed up Spring Boot on Kubernetes? A JDK 21 and JDK 25 study
A study of one Spring Boot workload found that mnemo-warmup reduced the application's reported startup time with both JDK 21 AppCDS and the JDK 25 AOT cache. With a 1 CPU and 1 GiB application limit, the median fell from 3.14 to 1.90 seconds on JDK 21 and from 3.23 to 1.47 seconds on JDK 25. In both cases, the median Kubernetes pod start-to-Ready remained 5 seconds.
That distinction matters. A JVM can start the application faster while container setup, archive fetching, readiness probes, and timestamp resolution leave the observed pod readiness time unchanged. Measure both before deciding whether a startup cache is useful for your service.
This article describes a benchmark of mnemo-warmup, an open-source tool for training and distributing Java startup caches in Kubernetes. It is based on the study report and raw results. The results are measurements from one workload and environment, not a prediction for every Java application.
What is mnemo-warmup?
mnemo-warmup trains a JVM startup cache using an application's container image in a Kubernetes cluster. It stores the archive in an OCI registry and arranges for later pods to fetch a compatible archive at startup. A fingerprint of inputs such as the JDK, application JARs, and JVM options helps decide whether an archive fits the launch.
The tool supports AppCDS, which shares prepared class metadata, and the JDK's AOT cache. In this study, its automatic selection used AppCDS with JDK 21 and AOT with JDK 25. The application remains a JVM application; this is a different deployment choice from compiling it to a GraalVM native executable.
Training runs a real instance of the application. That means startup actions, such as connecting to services or applying migrations, can also happen during training. Operators need to review the trainer configuration and measure the deployment overhead as well as the JVM saving. The project describes itself as alpha in its README.
The experiment
The benchmark used the same Spring Boot application, containing 70 JARs and about 59 MiB of application files, on Temurin JDK 21.0.13 and JDK 25.0.5. It ran on an Apple M5 laptop using Docker Desktop and a single-node, ARM64 kind cluster. The cache registry was local to the Docker network.
There were four configurations: each JDK with no application resource limits, and each JDK with application requests and limits of 1 CPU and 1 GiB. For each configuration, the harness discarded a warm-up round and measured 12 rounds of three interleaved variants:
- A pod without mnemo-warmup.
- A pod with mnemo-warmup but no archive in its store.
- A pod with mnemo-warmup and a trained archive.
The cached variant reported a successful fetch and archive use in all 12 measured pods in each configuration. The measurements below compare the first and third variants. The full study report includes the no-cache variant, ranges, environment, training records, registry samples, and links to raw pod data and logs.
Results: application startup and pod readiness
All values are medians. Application startup is taken from the Spring startup log. Pod start-to-Ready uses Kubernetes timestamps, which have whole-second resolution in this setup.
| Configuration | Application start without tool | Application start with archive | Pod start-to-Ready without / with archive |
|---|---|---|---|
| JDK 21 AppCDS, no application limits | 1,328 ms | 693 ms | 3.0 / 3.0 s |
| JDK 25 AOT, no application limits | 1,352 ms | 523 ms | 2.5 / 3.0 s |
| JDK 21 AppCDS, 1 CPU / 1 GiB | 3,142 ms | 1,901 ms | 5.0 / 5.0 s |
| JDK 25 AOT, 1 CPU / 1 GiB | 3,234 ms | 1,471 ms | 5.0 / 5.0 s |
Under the application limit, the measured median JVM startup saving was 1,242 ms for JDK 21 AppCDS and 1,763 ms for JDK 25 AOT, each compared with the no-tool baseline on the same JDK. Those are approximately 40% and 55% reductions in the application's reported startup time, respectively. They are not reductions in pod start-to-Ready: both limited cached and uncached deployments had a median of 5 seconds.
The no-limits JDK 25 comparison also shows why a faster application alone is not enough to establish a faster pod: its median cached pod start-to-Ready was 3.0 seconds, compared with 2.5 seconds without the tool. The timestamps and readiness probe in this experiment have approximately one-second resolution, so treat small differences with care.
What did the caches cost?
The AppCDS archive was about 87 MB in both JDK 21 runs. The JDK 25 AOT archive was about 109 MB. Five direct downloads from the kind node to the local registry had median times of roughly 24 ms for AppCDS and 30 ms for AOT. These are local-network results, not remote-registry performance measurements.
The trainer container ran for 4 seconds in each JDK 21 run and for 5 and 6 seconds in the two JDK 25 runs. Those are single observations, measured from container start to finish. They include work beyond producing the archive. The trainer had no CPU or memory limits, even when the application pods were limited.
When might mnemo-warmup be worth evaluating?
Consider a measured trial if you run Java services on Kubernetes, want to retain JVM deployment, and application startup is a meaningful part of rollout or scale-out time. A cluster-trained cache may be useful when the cluster's actual JDK and launch options differ from assumptions made during an image build. Compare against any cache already in the image; another training step and archive download may add cost without enough benefit.
Use caution when a workload already supplies JVM cache flags. In a separate local OpenJDK 25.0.1 check, combining -XX:AOTCache in JAVA_TOOL_OPTIONS with -XX:SharedArchiveFile on the command line stopped JVM initialization. The project's operations guide describes that limit. Preview warnings identify possible cache markers; they do not establish which flags a Kubernetes workload will ultimately use or guarantee compatibility.
If pod setup, network downloads, readiness checks, or application work outside class loading dominate startup, a faster JVM start may not improve the time users wait for a ready pod. Teams already satisfied with an image-baked cache or a native executable should benchmark those choices against the cluster-trained approach on their own workload.
What this study does and does not establish
- It measures one Spring Boot application, one ARM64 laptop and one local kind cluster. Results may differ on other applications, processors and production nodes.
- JDK version and cache mechanism changed together. The experiment does not isolate AppCDS versus AOT on the same JDK.
- The three variants were interleaved within each configuration, but JDK configurations were run sequentially. Machine conditions can drift between them.
- The registry was local, images were already available, and pods started one at a time. The study does not measure remote registry latency, image pulls, or concurrent scale-out.
- One limited JDK 21 no-cache application start took 21.3 seconds. It remains in the raw data and reported ranges.
How to evaluate it on your application
Start with the mnemo-warmup README, then read the benchmark method and results and operations guide. Compare repeated deployments with and without the tool under representative CPU and memory limits. Record application startup, pod start-to-Ready, archive size, download time, training cost, and failure behavior. Include any existing image-baked cache as its own baseline.
For this workload, mnemo-warmup made the Java application report startup sooner. The experiment did not show a lower median pod readiness time in the limited configuration.


