Files
spring-boot-demo/spring-boot-startup-time
Ankur Mhatre 958b401f0f Spring Boot startup time: bean-by-bean diagnosis, and one directory per post
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.
2026-09-05 00:17:37 +05:30
..

Why your Spring Boot app takes 8 seconds to start

Companion project for Why Your Spring Boot App Takes 8 Seconds to Start: A Bean-by-Bean Diagnosis.

Every millisecond figure in the article came out of this project. The transcripts are in docs/output/ and are regenerated by one command.


Versions

Resolved by the build, not read from documentation — see docs/output/00-versions.txt.

Component Version Notes
Spring Boot 4.1.1 GA 20 August 2026
Spring Framework 7.0.9 via spring-boot-starter-parent
Spring Data JPA 4.1.1
Hibernate ORM 7.4.5.Final
Tomcat (embedded) 11.0.24
Micrometer 1.17.1
H2 2.4.240
JDK Temurin 25.0.4.1+1 LTS AOT cache needs 24+
Maven 3.9.11

Quickstart

export JAVA_HOME=/path/to/jdk-25
mvn -DskipTests package

# recording, and serving the self-time view at /diag/startup
java -Dstartup.tracking=buffering -jar target/startup-diagnosis-1.0.0.jar

curl -s 'localhost:8080/diag/startup?top=12'          # self time -- the useful list
curl -s  localhost:8080/actuator/startup              # GET peeks
curl -sX POST localhost:8080/actuator/startup         # POST drains. Only once.

Regenerate every transcript (~20 minutes):

JAVA_HOME=/path/to/jdk-25 ./scripts/run-all.sh

Switches

Tracking is a system property, not a Spring property, because ApplicationStartup must be set before run(). See docs/02.

Switch Effect
-Dstartup.tracking=buffering BufferingApplicationStartup (default in this project)
-Dstartup.tracking=jfr FlightRecorderApplicationStartup; pair with -XX:StartFlightRecording
-Dstartup.tracking=none no tracking — the uninstrumented baseline
-Dstartup.buffer=2048 buffer capacity in steps; too small truncates silently
--spring.profiles.active=lazy spring.main.lazy-initialization=true

Endpoints

Endpoint What it gives you
/diag/startup?top=N this project's own — self time, phase totals, both orderings
/actuator/startup Boot's flat timeline. GET peeks, POST drains
/actuator/health, /actuator/beans, /actuator/conditions exposed for context
/orders/summary the application's actual job

/diag/startup exposes bean names and wiring. Delete it before shipping.


Documentation

# Chapter
01 The number Boot logs, and what it hides
02 Turning instrumentation on
03 The four phases hiding inside one number
04 Reading the step tree: self time versus total time
05 JFR instead of a buffer
06 The classpath-scan tax, measured
07 Failure modes
08 What actually helps

Captured output

File Scenario
00-versions.txt resolved versions from the build
01-api-corrections.txt javap and the compiler errors that corrected the code
02-startup-tree.txt the step tree by self time and by total time
03-jfr.txt the same startup as JFR events, read with jfr
04-scan-tax.txt +5000 classes, with and without @Component
05-what-helps.txt tracking overhead, lazy init, JDK 25 AOT cache
06-buffer-overflow.txt what a 2048-step buffer silently loses

Step counts and orderings are reproducible. Timings are indicative and drift by a few hundred milliseconds between runs on the same machine — the ratios are the result, not the absolute numbers.


Layout

pom.xml
scripts/
  run-all.sh                 regenerate everything under docs/output/
  run.sh / stop.sh           start and stop; stop.sh matches java+jar, never the main class
  gen-bulk.sh                generate N classes into the scanned package
  demo-*.sh                  one script per captured scenario
src/main/java/com/ankurm/startup/
  slow/                      four beans that do real work on the way up
  domain/                    one entity, one repository with derived queries
  web/                       StartupDiagnosticsEndpoint (self time) + the business controller
src/test/java/               contract tests for drain, truncation and double-counting
docs/                        numbered chapters
docs/output/                 captured real output

Licence

MIT — see LICENSE.