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.
This commit is contained in:
133
spring-boot-startup-time/README.md
Normal file
133
spring-boot-startup-time/README.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# 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](post/post.md)**.
|
||||
|
||||
Every millisecond figure in the article came out of this project. The transcripts are in
|
||||
[`docs/output/`](docs/output) and are regenerated by one command.
|
||||
|
||||
---
|
||||
|
||||
## Versions
|
||||
|
||||
Resolved by the build, not read from documentation — see
|
||||
[`docs/output/00-versions.txt`](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
|
||||
|
||||
```bash
|
||||
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):
|
||||
|
||||
```bash
|
||||
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`](docs/02-turning-instrumentation-on.md).
|
||||
|
||||
| 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](docs/01-the-number-boot-logs.md) |
|
||||
| 02 | [Turning instrumentation on](docs/02-turning-instrumentation-on.md) |
|
||||
| 03 | [The four phases hiding inside one number](docs/03-the-four-phases.md) |
|
||||
| 04 | [Reading the step tree: self time versus total time](docs/04-reading-the-step-tree.md) |
|
||||
| 05 | [JFR instead of a buffer](docs/05-jfr-instead-of-a-buffer.md) |
|
||||
| 06 | [The classpath-scan tax, measured](docs/06-the-classpath-scan-tax.md) |
|
||||
| 07 | [Failure modes](docs/07-failure-modes.md) |
|
||||
| 08 | [What actually helps](docs/08-what-actually-helps.md) |
|
||||
|
||||
---
|
||||
|
||||
## Captured output
|
||||
|
||||
| File | Scenario |
|
||||
|---|---|
|
||||
| [`00-versions.txt`](docs/output/00-versions.txt) | resolved versions from the build |
|
||||
| [`01-api-corrections.txt`](docs/output/01-api-corrections.txt) | `javap` and the compiler errors that corrected the code |
|
||||
| [`02-startup-tree.txt`](docs/output/02-startup-tree.txt) | the step tree by self time and by total time |
|
||||
| [`03-jfr.txt`](docs/output/03-jfr.txt) | the same startup as JFR events, read with `jfr` |
|
||||
| [`04-scan-tax.txt`](docs/output/04-scan-tax.txt) | +5000 classes, with and without `@Component` |
|
||||
| [`05-what-helps.txt`](docs/output/05-what-helps.txt) | tracking overhead, lazy init, JDK 25 AOT cache |
|
||||
| [`06-buffer-overflow.txt`](docs/output/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](../LICENSE).
|
||||
Reference in New Issue
Block a user