Files
spring-boot-demo/startup-runners/README.md
T

4.2 KiB

startup-runners

Companion project for the article CommandLineRunner vs ApplicationRunner vs ApplicationReadyEvent in Spring Boot 4 on 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

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()