# 05 — JFR instead of a buffer ← prev [04 — Reading the step tree](04-reading-the-step-tree.md) · next → [06 — The classpath-scan tax](06-the-classpath-scan-tax.md) --- `FlightRecorderApplicationStartup` lives in `spring-core`, needs no dependency, and emits each `StartupStep` as a JFR event. Swap the tracker and start a recording: ```bash java -XX:StartFlightRecording=filename=startup.jfr,settings=profile,dumponexit=true \ -Dstartup.tracking=jfr -jar app.jar ``` Everything below is from [`docs/output/03-jfr.txt`](output/03-jfr.txt). ## The event type ``` @Name("org.springframework.core.metrics.jfr.FlightRecorderStartupEvent") @Category("Spring Application") @Label("Startup Step") @Description("Spring Application Startup") class FlightRecorderStartupEvent extends jdk.jfr.Event { ... } ``` In JDK Mission Control it appears under the **Spring Application** category. On the command line, the name matters more than you would expect: ``` --events StartupEvent -> 0 events --events 'org.springframework.core.metrics.jfr.FlightRecorderStartupEvent' -> 398 events ``` `jfr print --events` matches the `@Name`, which is the fully qualified class name. The short form silently returns nothing — no error, no warning, just an empty result that reads like "Spring did not record anything". ## Reading it without Mission Control ```bash jfr summary startup.jfr jfr print --events 'org.springframework.core.metrics.jfr.FlightRecorderStartupEvent' \ --json startup.jfr ``` The eight slowest, sorted by the recording's own `duration` field: ``` duration name / tags PT6.544849841S spring.context.refresh PT2.263845435S spring.beans.instantiate beanName=&entityManagerFactory,... PT1.500763631S spring.context.beans.post-process PT1.202591188S spring.context.beandef-registry.post-process postProcessor=...ConfigurationClassPostProcessor@76b224cd PT1.176668418S spring.context.config-classes.parse classCount=130 PT0.538651275S spring.beans.instantiate beanName=reportTemplateRegistry PT0.535484510S spring.beans.instantiate beanName=tariffCacheWarmer PT0.515340537S spring.boot.webserver.create factory=...TomcatServletWebServerFactory ``` (That run started in 7.441 s; the buffered run in [chapter 04](04-reading-the-step-tree.md) started in 6.6 s. Same jar, different runs — see [chapter 07](07-failure-modes.md) on why a single measurement is not a result.) Note that this is the *total time* list from [chapter 04](04-reading-the-step-tree.md), with `spring.context.refresh` on top and `reportTemplateRegistry` above `tariffCacheWarmer`. JFR gives you durations and parent ids; it does not compute self time either. The same subtraction applies. ## What JFR buys you that the buffer does not Correlation. The recording holds the JVM's own view of the same seconds: ``` jdk.ExecutionSample 345 jdk.GCPhasePauseLevel1 132 jdk.GCPhasePause 39 (289.8 ms of pause in total) jdk.Compilation 16 jdk.ClassLoaderStatistics 10 ``` If a bean's constructor is slow because a young collection landed in the middle of it, the buffer shows you a slow bean and JFR shows you why. That is the case for using it. ## What it costs you - The recording is written to a file on the machine, not served over HTTP. In a container that means a volume or a copy out. - The `tags` are serialised into one flat `String` attribute, because JFR events only carry base types. Parsing `beanName=x,beanType=y,` is on you. - `settings=profile` records a great deal more than startup steps. For a 7-second startup that is fine; left on in production it is not. ## Buffer or JFR? Use the buffer when the question is "which bean", and you can reach the application over HTTP. Use JFR when the question is "why is that bean slow", when startup fails before the web server is up (the buffer is unreachable then; the JFR file is not), or when you need the same recording to answer a GC question. --- ← prev [04 — Reading the step tree](04-reading-the-step-tree.md) · next → [06 — The classpath-scan tax](06-the-classpath-scan-tax.md)