# 03 — The four phases hiding inside one number ← prev [02 — Turning instrumentation on](02-turning-instrumentation-on.md) · next → [04 — Reading the step tree](04-reading-the-step-tree.md) --- Grouping every recorded step by name and summing **self time** (see [chapter 04](04-reading-the-step-tree.md) for why self and not total) gives the shape of a startup. From [`docs/output/02-startup-tree.txt`](output/02-startup-tree.txt): ``` self ms count step 4253.68 361 spring.beans.instantiate 1014.51 1 spring.context.config-classes.parse 402.81 1 spring.boot.application.environment-prepared 160.39 1 spring.boot.webserver.create 153.07 1 spring.boot.application.started 113.74 1 spring.context.refresh 95.72 1 spring.data.repository.proxy 51.50 6 spring.beans.smart-initialize 43.85 1 spring.data.repository.scanning ``` Four phases account for essentially all of it, and they respond to entirely different fixes. ## 1. Bean instantiation — `spring.beans.instantiate` 361 steps, 4.25 s. This is constructors, `@PostConstruct`, `FactoryBean.getObject()` and proxy creation. It is where your own code lives, and it is the only phase you can fix by changing application code. Note what is *not* a separate step: `@PostConstruct` has no step of its own, so a cache warm shows up inside the owning bean's `spring.beans.instantiate`. `tariffCacheWarmer` in this repository spends all of its 504 ms in `@PostConstruct` and none in its constructor, and the timeline cannot tell you which. ## 2. Configuration class parsing — `spring.context.config-classes.parse` One step, 1.01 s, and under JFR it carries a tag naming the cost driver: ``` PT1.176668418S spring.context.config-classes.parse classCount=130, ``` 130 configuration classes. This step is component scanning plus `@Conditional` evaluation across every auto-configuration your classpath drags in. It is proportional to *classes inspected*, not to beans created — see [chapter 06](06-the-classpath-scan-tax.md), where adding 5,000 classes that are not beans at all still adds half a second here. ## 3. Environment preparation — `spring.boot.application.environment-prepared` 403 ms before any bean exists. Property sources, profile resolution, config data imports. Cheap to ignore and impossible to tune from application code, but it explains why "the app does nothing for the first half second". ## 4. Everything else `spring.boot.webserver.create` (160 ms), the Spring Data repository steps (~150 ms across five step names), `spring.beans.smart-initialize`. Individually small; collectively a second. Spring Data's contribution scales with the number of repository *interfaces* and derived query methods, since each one is parsed and proxied. ## The step names, in the order they nest ``` spring.boot.application.starting spring.boot.application.environment-prepared spring.boot.application.context-prepared spring.context.refresh ├── spring.context.beandef-registry.post-process │ └── spring.context.config-classes.parse ← scanning + conditions ├── spring.context.bean-factory.post-process ├── spring.context.beans.post-process │ └── spring.beans.instantiate (×N, nested by dependency) └── spring.boot.webserver.create spring.boot.application.started spring.boot.application.ready ``` A step is recorded **when it ends**, which is why the outermost ones appear last in the timeline — and why they are the first casualties of a full buffer ([chapter 07](07-failure-modes.md)). --- ← prev [02 — Turning instrumentation on](02-turning-instrumentation-on.md) · next → [04 — Reading the step tree](04-reading-the-step-tree.md)