The Application Lifecycle & Runners
The Application Lifecycle & Runners
Every Spring Boot application goes through a well-defined lifecycle from the moment SpringApplication.run() is called to the moment the JVM exits. Understanding that lifecycle lets you hook into exactly the right phase — whether you need to run a database check before the first request, seed initial data, or perform a clean shutdown. This lesson walks through the internal startup sequence and the two runner interfaces — CommandLineRunner and ApplicationRunner — that are the recommended way to execute code once the application context is fully initialised.
What Happens Inside SpringApplication.run()
When your main method calls SpringApplication.run(MyApp.class, args), the following sequence takes place (simplified):
- Bootstrap phase: Spring loads
SpringApplicationRunListeners and firesstarting(). - Environment preparation:
application.properties/.yml, environment variables, and command-line arguments are merged into anEnvironment. - Context creation: The correct
ApplicationContexttype is created (AnnotationConfigServletWebServerApplicationContextfor a web app,AnnotationConfigApplicationContextfor a non-web app). - Bean definition loading: Your
@Configurationclasses are processed; all bean definitions are registered. - Context refresh: Every bean is instantiated, dependencies are injected,
@PostConstructcallbacks run, and the embedded server starts. - Runners execute: All
CommandLineRunnerandApplicationRunnerbeans are invoked, in order. - Ready:
ApplicationReadyEventis published. The application is serving requests.
CommandLineRunner
CommandLineRunner is a single-method functional interface. Its run(String... args) method receives the raw command-line arguments exactly as passed to main().
Because it is a plain @Component, it participates in dependency injection like any other bean. The constructor receives UserRepository — no field injection required.
ApplicationRunner
ApplicationRunner is almost identical but receives an ApplicationArguments object instead of a raw String[]. This is more convenient when you pass structured arguments on the command line.
ApplicationArguments distinguishes between option arguments (prefixed with --, like --env=prod) and non-option arguments (plain strings like migrate). This removes the need to parse String[] by hand.
Controlling Execution Order
When you have multiple runners, Spring Boot executes them in the order defined by the @Order annotation (lower value = higher priority) or the Ordered interface.
@Order to make the dependency explicit in code rather than relying on bean registration order, which is not guaranteed and changes silently as the codebase grows.
Runners vs. @PostConstruct vs. ApplicationListener
Spring gives you several hooks. Here is when to use each one:
@PostConstruct— runs immediately after a single bean is constructed, before other beans that depend on it are fully initialised. Use it for bean-local setup (initialising an internal cache, validating a configuration property). Do not use it for anything that requires other beans to be fully started (e.g. starting a background thread that touches the DB).CommandLineRunner/ApplicationRunner— run after the entire context is refreshed and the server is up. Use for startup tasks that touch multiple beans or the outside world: database migrations, cache warming, health checks, CLI-style commands.ApplicationListener<ApplicationReadyEvent>— fires at exactly the same point as runners, but via the event system. Useful when the startup logic lives in an infrastructure layer that should not know about the Runner interfaces. Runners are generally simpler and preferred for application-level code.
Practical Pattern: Conditional Seeding
A common real-world pattern is seeding data only when a Spring profile is active, avoiding accidental data insertion in production:
System.exit(1). This is usually what you want for a fatal startup check, but surprising if the exception is accidental. Handle expected errors explicitly and only let truly unrecoverable problems propagate.
Testing Runners
You can exclude runners from specific tests by excluding the bean class or using @SpringBootTest with a selective component scan. Alternatively, test the runner directly by constructing it with mock dependencies:
Summary
Spring Boot's startup sequence is deterministic and well-ordered. CommandLineRunner is the go-to interface when you just need to run code after startup and have no structured command-line arguments. ApplicationRunner is the better choice whenever you want to parse --option=value style arguments cleanly. Both are plain Spring beans: inject what you need, annotate with @Order when sequencing matters, and gate with @Profile to keep production safe. In the next lesson you will look at Spring Boot's built-in logging system and how to configure it without touching logback.xml.