Yes for startup in this experiment, but not for everything. In a controlled comparison, Spring Boot's official extracted layout reduced median Spring-reported startup from 11.056 s to 8.476 s (-23.3%) and median first-request latency from 2.195665 s to 1.447709 s (-34.1%). All six planned pairs moved in the same direction. Later requests, RSS, and swap did not show a material winner under the thresholds fixed before measurement.
That is an engineering result for one application, JDK, VM, and launch profile. It is not a claim that extraction always makes Spring Boot faster.
The published experiment record contains the method, JVM flags, paired-run evidence, operational follow-up, and limitations. Full archives, databases, and private metadata are omitted.
Why test the layout?
Earlier constrained-memory work showed that Spring Boot could run on a 256 MiB machine, but startup was relatively slow and the environment could feel borderline. That raised a specific suspicion: perhaps executable fat-JAR and nested-JAR class loading was contributing meaningful startup overhead.
This experiment tests that suspicion by changing only the runtime layout. The 256 MiB Alpine VM is a deliberately demanding environment where startup overhead matters. It is not the article's subject by itself.
What was compared
A Spring Boot 4.1.1 application was built once as an executable fat JAR. The baseline launched that JAR normally. The variant was produced from that exact JAR with Spring Boot's official jarmode=tools extraction and launched its discovered application JAR with the same JVM options, arguments, data, and working directory. The two layouts used the same 86 dependency JARs on the same guest filesystem.
The application renders a deterministic local H2-backed dashboard at GET /. The measured request does not contact GitHub or another external service. A neutral copy of H2 performs the database reset before each launch, so the reset does not warm either layout's H2 file immediately before startup.
The test environment was Alpine 3.23.4 x86_64 with OpenJDK 25.0.4, one vCPU, 256 MiB configured RAM, and a fixed 512 MiB swapfile. The JVM profile was:
-Xms16m -Xmx80m -Xss256k -XX:+UseSerialGC
-XX:TieredStopAtLevel=1 -XX:ReservedCodeCacheSize=32m
-XX:+UseCompactObjectHeaders
There were six fresh starts per layout in alternating paired order: fat, extracted, extracted, fat, repeated three times. Each start had one timed first request, five following requests, and a five-minute observation. StatLite and other monitoring clients were absent from all twelve comparison runs. The experiment did not claim a cold page cache; it used the same reset and alternation procedure for every run.
Results
There were six fresh starts per layout, twelve starts total. The visual summary should make the main contrast immediate: startup and first request move substantially, while settled memory does not.
Points are individual runs; the short bar is the median.
Spring startup (s)
-23.3% median (11.056 s to 8.476 s)
First request (s)
-34.1% median (2.196 s to 1.448 s)
Settled JVM RSS (KiB)
-3.8% median, not material (zero-based scale)
All twelve runs survived their five-minute observation and returned valid responses. The twelve-run table is in the published analysis. Times are seconds except external startup (milliseconds), RSS, and swap (KiB). The medians and ranges were:
| Metric | Fat median (range) | Extracted median (range) | Extracted minus fat |
|---|---|---|---|
| Spring startup | 11.056 s (10.477–11.560) | 8.476 s (8.029–8.993) | -2.580 s (-23.3%) |
| External startup | 11910 ms (11270–12490) | 9000 ms (8560–9560) | -2910 ms (-24.4%) |
| First request | 2.195665 s (1.946922–2.436299) | 1.447709 s (1.354330–1.656602) | -0.747956 s (-34.1%) |
| Median requests 2–6 | 0.020696 s | 0.019139 s | -7.5% |
| Peak RSS | 160064 KiB | 155570 KiB | -2.8% |
| Settled RSS | 137212 KiB | 131952 KiB | -3.8% |
| Swap delta | 145347 KiB | 137652 KiB | -5.3% |
The startup and first-request differences clear both the absolute and relative materiality thresholds, and every paired comparison favors extracted. Memory and swap did not show a material winner. Extracted had slightly lower median RSS and swap, but the differences were below the experiment's materiality thresholds and were not consistently reproduced across measurements. Peak RSS favored extracted in five of six pairs; settled RSS in four of six. The roughly 7.7 MiB swap difference is far below the frozen 16 MiB plus 10% threshold.
What happens after startup?
Requests 2–6 were much closer than the first request. Their per-run median was 0.020696 s for fat and 0.019139 s for extracted (-7.5%), which did not meet the materiality rule. The large difference is therefore concentrated in the startup and first-request path rather than in steady warm requests.
Why might extraction help?
The extracted layout keeps the application classes and dependency JARs as ordinary filesystem entries instead of requiring the launcher and class loader to work through nested entries in one executable archive. This experiment shows a repeatable startup and first-request difference under memory pressure, but it does not isolate which individual class-loading or filesystem action causes the difference. That would require a different experiment.
Separate operational follow-up
After selecting extracted from the primary result, I ran it once more with StatLite. It was started only after six clean requests completed. The clean startup was 6.904 s; clean request 1 was 0.950558 s. StatLite then polled the application and its own loopback metrics every 30 seconds for one hour while a guest-local request ran at the same cadence.
All 120 scheduled requests succeeded with HTTP 200 and the expected 11,423 bytes. Latency ranged from 5.513 ms to 106.305 ms (mean 14.729 ms), and the latest request was only 100 ms behind schedule. The application kept one PID and shut down cleanly, with no fatal or OOM evidence. Guest-wide MemAvailable varied from 30,892 to 178,716 KiB; SwapFree varied from 354,236 to 520,100 KiB and ended at 518,916 KiB.
This follow-up is operational evidence, not a thirteenth comparison sample. The sampler's background shell did not receive the updated application and StatLite PID variables, so its per-process RSS columns are unavailable. That limitation is preserved in the journal and raw evidence; it was not worth repeating an hour-long run because it cannot change the primary comparison or the observed request and survival result.
What this means for production
The deployment pattern is straightforward:
build fat JAR
→ extract during packaging/deployment
→ run extracted application JAR + lib/
Extraction is especially easy to adopt when deployment already creates a container image, VM image, release directory, or other versioned application bundle. When startup time matters, the extracted layout is a sensible production choice to try first, particularly on constrained deployments. Memory and swap did not show a material winner, so extraction should not be sold as an RSS or swap optimization. Validate it against the actual application and runtime profile, and do not expect the same percentages for every Spring Boot application.
Limits and conclusion
This is one representative application on one constrained VM, with one JDK distribution and one fixed JVM profile. It does not establish a universal Spring Boot performance rule, nor does it measure deployment extraction cost as part of startup. The result is narrower and more useful: for this setup, Spring Boot's supported extracted layout produced a large, repeatable startup and first-request improvement, while later-request latency and memory/swap changes were not materially different.
Secondarily, the extracted layout made the already-demonstrated 256 MiB deployment smoother at the point where startup was most expensive. That is context for this test, not a separate claim about Spring Boot hosting.
The method, JVM flags, paired-run evidence, and operational follow-up are in the public experiment record. Full archives, databases, and private metadata are omitted.
Running Spring Boot on a small VPS? StatLite provides application metrics and basic host visibility without a separate Prometheus/Grafana stack.