--- title: "MockServer Start-up Time" shortTitle: "Start-up Time" description: "MockServer starts in under a second by default. The standard image uses a baked-in class-data-sharing archive (~0.57 s ready); the -aot variant uses a JDK 25 Leyden AOT cache (~0.35 s). A built-in warmup step eliminates the first-request cold penalty." layout: page pageOrder: 4 section: 'Performance Testing' subsection: true sitemap: priority: 0.8 changefreq: 'monthly' lastmod: 2026-07-02T08:00:00+01:00 keywords: mockserver startup time, mockserver fast start, mockserver aot, mockserver appcds, mockserver testcontainers startup schema_faq: - question: "How fast does MockServer start?" answer: "The standard mockserver/mockserver Docker image reaches ready in roughly 0.57 s (measured on arm64, median of 5 cold starts). The -aot variant, which uses a JDK 25 Leyden AOT cache, reaches ready in roughly 0.35 s. Both figures include container overhead and are machine-relative. A built-in warmup step means the first request after port bind is answered in ~5–10 ms rather than the 200–350 ms a cold JVM would otherwise take." - question: "What is the -aot image and when should I use it?" answer: "The -aot image tags (e.g. mockserver/mockserver:latest-aot) ship a JDK 25 Leyden AOT class-metadata cache baked in at build time, cutting time-to-ready to roughly 0.35 s. They are the best choice when you start many short-lived containers and the ~0.2 s savings per container adds up. The AOT cache is CPU-architecture specific (a separate cache is baked per platform), and this variant uses the JDK TLS provider rather than netty-tcnative. In Testcontainers use MockServerContainer.aotImage() to select the -aot variant automatically." - question: "What is the startupWarmup property?" answer: "startupWarmup (default true) makes MockServer send one PUT /mockserver/status request to itself immediately after the port binds. This drives the full request-handling path through the JVM, loading the class burst that otherwise hits the first real caller. With warmup on, the first request from outside arrives in ~5–10 ms instead of 200–350 ms. Set -Dmockserver.startupWarmup=false or MOCKSERVER_STARTUP_WARMUP=false to disable it — useful if you are benchmarking cold-start behaviour." - question: "Does the AppCDS archive in the standard image affect how MockServer behaves?" answer: "No. Class-data sharing pre-loads class metadata so the JVM spends less time parsing and verifying class files. The runtime behaviour is identical to a standard JVM start. If the archive is ever unusable — for example if the image were rebuilt against a different JDK build — the JVM logs a warning and starts normally. There is no risk of incorrect behaviour from the archive." - question: "Why doesn't MockServer offer a GraalVM native-image distribution?" answer: "MockServer loads user-supplied classes at runtime: class callbacks, expectation initializers, and custom matchers. GraalVM native-image requires a closed world at build time, which is fundamentally incompatible with runtime class loading. In testing, features not explicitly traced during the build-time metadata step failed silently at runtime — HTTPS, templates, and logging each failed without reporting a clear error. The real HotSpot JVM is the only distribution that can provide 100% feature parity." ---
The standard mockserver/mockserver image reaches ready in roughly 0.57 s — about a third faster than MockServer 7.6.0 — thanks to a class-data-sharing archive baked in at build time. For the lowest possible container start, the -aot image variant uses a JDK 25 Leyden AOT cache and reaches ready in roughly 0.35 s. A built-in warmup step means the first real request you send is answered in ~5–10 ms rather than the hundreds of milliseconds a cold JVM would otherwise take. For test suites, the single biggest speed lever remains one container per suite with reset() between tests — that brings per-test cost to milliseconds regardless of image choice.
| Image | Time to ready (typical) | Notes |
|---|---|---|
| mockserver/mockserver (standard) | ~0.57 s | Default. Class-data-sharing archive baked at build time (JDK 17). Identical runtime behaviour to a plain JVM start; archive fallback if unusable. |
| mockserver/mockserver:<version>-aot | ~0.35 s | JDK 25 Leyden AOT cache. CPU-arch specific. JDK TLS provider (see below). |
| In-process (JVM test, no container) | Tens of ms | JVM already warm. No Docker overhead. See Fast Tests. |
Figures measured on arm64 Mac, medians of 5 cold starts, container-launch to first successful PUT /mockserver/status response. Absolute numbers are machine-relative; they shift on different hardware and JDK builds.
The standard mockserver/mockserver image includes a class-data-sharing (CDS) archive produced at image-build time. CDS lets the JVM map pre-parsed class metadata directly from a shared file, skipping the work of reading and verifying every class from the jar on each cold start. The archive was trained against the real MockServer startup path, so the classes that matter most arrive cheapest.
If the archive is ever unusable — for example because the image was rebuilt against a different JDK patch — the JVM logs a warning and starts normally. There is no risk of incorrect behaviour from the archive.
The image is built on a jlink-trimmed JDK 17 runtime on a distroless base, so the archive size (~33 MB) is offset by the smaller runtime: the complete image is 411 MB versus 422 MB for the pre-archive 7.6.0 image.
The -aot image tags (mockserver/mockserver:latest-aot, mockserver/mockserver:<version>-aot) go further: they bake a Project Leyden AOT class-metadata cache (JEPs 483, 514, 515) into the image. At build time the image starts MockServer as a training run, records which classes were loaded and how they were used, and writes the result into the image. At runtime the JVM reads the cache rather than loading and resolving classes from scratch.
Because it is the real HotSpot JVM, the -aot variant has 100% feature parity with the standard image — no closed-world limitations, no missing features at runtime. The same expectations, templates, TLS, and class callbacks all work. Unlike a native binary, there is no build-time metadata to maintain.
Caveats to be aware of:
Selecting the -aot image in Testcontainers:
// aotImage() returns the default image with -aot appended to the tag,
// staying in lockstep with the client library version.
try (MockServerContainer mockServer = new MockServerContainer(MockServerContainer.aotImage())) {
mockServer.start();
// use mockServer.getClient() as normal
}
// Or pin an explicit -aot tag:
try (MockServerContainer mockServer = new MockServerContainer(
DockerImageName.parse("mockserver/mockserver:{{ site.mockserver_version }}-aot"))) {
mockServer.start();
}
Even after the port binds and the container reports healthy, a cold JVM takes an extra 200–350 ms to answer the very first request. That penalty comes from a burst of ~217 classes that the JVM loads the first time it runs the full request-handling path — Netty HTTP encoders, Jackson serializers, response writers. Every subsequent request avoids that cost entirely.
MockServer eliminates this penalty automatically. Immediately after the port binds, a background thread issues one internal PUT /mockserver/status request, which drives the full request-handling path and loads that class burst before any real caller arrives. The result: the first request you send from outside lands in ~5–10 ms instead of 200–350 ms.
The warmup runs as a daemon thread so it never delays JVM shutdown. If it fails for any reason (network not yet available, timeout) it logs the failure at TRACE level and moves on — the server continues normally. The warmup request goes through the same pre-authentication path as health checks and Kubernetes readiness probes, so it works even when control-plane authentication is enabled.
Configuration. The warmup is on by default. To disable it — for example when benchmarking cold-start behaviour — set:
Full property reference: Startup Warmup — Configuration Properties.
Two subsystems that are only needed in some deployments now initialise on demand rather than at startup:
The largest speed gain available in a test suite is architectural, not runtime: start one container for the whole suite and call client.reset() between tests. reset() clears all expectations and recorded requests in milliseconds, giving the same isolation as a fresh container without paying the startup cost per test. For JVM tests that need no network isolation, running MockServer in-process eliminates container overhead altogether.
See Fast Tests: Startup Time, Memory and Reuse for complete examples including suite-scoped containers, withReuse(true) for local development, memory caps, and when to choose in-process versus containerised.
Wait strategy. The default Testcontainers wait strategy (HTTP GET on /mockserver/status) is fine as-is. Because the built-in warmup runs before the first external request arrives, by the time the wait strategy polling period elapses the server has already warmed itself and the first real request you send will be fast.
All numbers on this page come from a measurement harness in scripts/perf/. The methodology:
Several JVM startup technologies are maturing in parallel. The -aot image already uses JDK 25 Leyden (JEPs 483, 514, 515). Here is where the broader landscape sits:
| Technology | JDK status | What it does |
|---|---|---|
| Project Leyden — class-metadata cache (JEPs 483, 514, 515) | GA in JDK 24/25 | Pre-loads class loading, linking, and AOT profiling. The -aot image uses this today. |
| Project Leyden — AOT code compilation (draft JEP 8335368) | No release target yet | Would pre-compile JIT output into the AOT cache, reducing time-to-peak performance as well as startup time. |
| CRaC (Checkpoint/Restore in Userspace) | Azul and Liberica JDKs only — not mainline OpenJDK | Snapshots a running JVM and restores from checkpoint in ~50 ms. Requires a non-mainline JDK and the checkpoint contains environment state (credentials, CA keys) that must be regenerated on restore. |
| GraalVM native-image | Community/commercial product, not mainline JDK | Compiles to a native binary; no JVM at runtime. MockServer does not offer a native-image distribution because its feature set depends on runtime class loading (class callbacks, expectation initializers, custom matchers) which is incompatible with native-image's closed-world assumption. In evaluation, features not explicitly traced during build-time metadata collection failed silently at runtime without reporting a clear error. The supported lightweight distribution for JVM-less environments is the jlink binary bundle. |