# startup-runners Companion project for the article **[CommandLineRunner vs ApplicationRunner vs ApplicationReadyEvent in Spring Boot 4](https://ankurm.com/commandlinerunner-vs-applicationrunner-vs-applicationreadyevent-spring-boot-4/)** on **[ankurm.com](https://ankurm.com)**. Every console block quoted in the article came out of `output/`. Transcripts 00-14 are written by the test suite (so a claim that stops being true turns the build red); 15-17 are read out of the Spring Boot and Spring Framework sources and jars by `scripts/capture-facts.sh`. There is deliberately **no `docs/` folder**: the deeper material lives in collapsible sections inside the article. ## Versions | | | |---|---| | Spring Boot | 4.1.2 (GA 2026-10-22 on Maven Central) | | Spring Framework | 7.0.10 | | JDK | 25 (25.0.3) | | Maven | 3.9 | | Sources read for contrast | Spring Boot 3.0.13 and 3.1.12 (`callRunners` only, not run) | ## Quickstart ```bash export JAVA_HOME=/path/to/jdk-25 mvn test # runs the scenarios and rewrites output/00-14 ./scripts/run-all.sh # everything, including 15-17 (needs network access to Maven Central once) ``` Transcript 03 (four runners without `@Order`) is the one file that is allowed to differ between runs: that is its point. ## Source layout Each scenario is its own package with its own `@SpringBootApplication`. | Package | What it holds | |---|---| | `hello/` | the smallest example: a `CommandLineRunner`, an `ApplicationRunner`, an `ApplicationReadyEvent` listener | | `order/` | every startup hook plus `@PostConstruct`, `InitializingBean`, `SmartLifecycle`, printing the sequence and the readiness state | | `ordering/`, `ties/` | `@Order` on classes, `@Bean` methods and `Ordered`; four runners with no order at all | | `args/` | the same command line seen as `String[]` and as `ApplicationArguments` | | `failure/` | a runner that throws, an `ApplicationReadyEvent` listener that throws, `ExitCodeGenerator`, a checked exception (profiles) | | `blocking/` | a runner that blocks (web app, actuator probes) and one that hands its work to a virtual thread | | `lifecycle/`, `lifestartfails/`, `lifeforgetful/` | `SmartLifecycle` phases, a `start()` that throws, an `isRunning()` that never says true | | `slices/` | startup hooks declared four ways, for the `@SpringBootTest` and `@WebMvcTest` scenarios | | `support/` | `Trace`, the in-memory log the tests print | ## Captured output | File | What it shows | |---|---| | `00-hello.txt` | the three hooks of `HelloApp`, started with a real command line | | `01-startup-hooks-in-order.txt` | every hook, in order, non-web and web, with the readiness state each one sees | | `02-order-annotation-and-ordered.txt` | both runner kinds sorted together by `@Order` / `Ordered`; a bean that is both kinds | | `03-ties-have-no-promised-order.txt` | four unordered runners in 8 fresh JVMs | | `04-arguments.txt` | `String[]` versus `ApplicationArguments` | | `05-runner-failure.txt` | a throwing runner: events, log, exit codes (1, 42), checked-exception wrapping | | `06-ready-listener-failure.txt` | a throwing `ApplicationReadyEvent` listener, non-web and web | | `07-blocking-runner-readiness.txt` | a blocking runner: port open, liveness UP, readiness 503 | | `08-async-runner.txt` | a runner that returns at once and a background task that fails | | `09-smartlifecycle-phases.txt` | start in ascending phase, stop in descending phase, async stop, a bean that is not auto-started | | `10-lifecycle-beans-in-a-web-app.txt` | every `SmartLifecycle` bean in a web app with its phase | | `11-smartlifecycle-start-fails.txt` | `start()` throws: no started event, no runner | | `12-smartlifecycle-forgot-isrunning.txt` | `isRunning()` always false: `stop()` never called | | `13-spring-boot-test-runs-the-hooks.txt` | what `@SpringBootTest` runs | | `14-webmvctest-slice-hooks.txt` | what `@WebMvcTest` runs | | `15-callrunners-source.txt` | `SpringApplication.run`, `callRunners`, `callRunner` and the run listener, from the 4.1.2 sources, plus 3.0.13 and 3.1.12 | | `16-javap-callrunners.txt` | the compiled `callRunners` and the runner interfaces, read with `javap` | | `17-lifecycleprocessor-source.txt` | when `DefaultLifecycleProcessor` calls `start()` and `stop()` |