Adds spring-boot-startup-time/, the companion project for BLOG-618: a runnable Spring Boot 4.1.1 application on JDK 25 that installs BufferingApplicationStartup and FlightRecorderApplicationStartup behind a system property, and a /diag/startup endpoint that computes step self time -- the number /actuator/startup does not give you and the one that names the actual culprits. Captured under docs/output/: the step tree sorted both ways, the same startup as JFR events, a +5000-class experiment putting 0.11 ms per scanned class on the classpath scan tax, the silent truncation a 2048-step buffer performs, and JDK 25 AOT cache timings (6.93 s to 4.82 s). Post body and metadata live in post/. Moves the existing Actuator project into actuator-in-production/ so the repository holds one directory per article; the root README is now an index.
2.4 KiB
01 — The number Boot logs, and what it hides
next → 02 — Turning instrumentation on
Every Spring Boot application ends its startup with one line:
Started StartupDiagnosisApplication in 6.6 seconds (process running for 7.4)
Two numbers. The first is measured from the SpringApplication.run() call to the
ApplicationReadyEvent. The second is ManagementFactory.getRuntimeMXBean().getUptime(),
so the gap between them is JVM bootstrap: opening the jar, verifying classes, starting the
JIT — work that happens before your code runs at all.
Neither number tells you where the 6.6 seconds went, and the usual next move — reading the
log timestamps — is worse than it looks. The log only shows you components that chose to
log. This application spends about half a second inside tariffCacheWarmer and logs
nothing while doing it. On the timeline it is a silent gap between two Hibernate lines,
and you would reasonably conclude that Hibernate was slow.
What is actually available
Spring Framework has carried an ApplicationStartup SPI since 5.3. It is a tracing
interface with one method that matters:
public interface ApplicationStartup {
StartupStep start(String name);
}
Framework and Boot call it at about two dozen named points — spring.beans.instantiate,
spring.context.config-classes.parse, spring.data.repository.proxy, and so on — tagging
each step with the bean name or class count involved. Steps nest, so what you get is a
tree, not a list.
The default implementation, DefaultApplicationStartup, does nothing. That is the whole
reason startup profiling feels unavailable: the instrumentation is already in your
application and is switched off.
Two implementations record it:
| Implementation | Where it lives | Output |
|---|---|---|
BufferingApplicationStartup |
spring-boot |
in-memory buffer, read over HTTP |
FlightRecorderApplicationStartup |
spring-core |
JFR events in a .jfr file |
There is no third option and no third-party agent needed.
What this repository measures
The application here is deliberately ordinary: web, JPA over H2, three repositories, and
four beans that do real work on the way up. It starts in about 6.6 seconds on the machine
that produced docs/output/. Every figure in the article and in these chapters
came from a file in that directory.