Hook: Spring Boot startup is like opening a restaurant: the kitchen is stocked first, the doors unlock next, and only then do customers walk in.
Question: Explain Spring Boot startup flow.
Answer: Spring Boot startup begins in the main() method when you call SpringApplication.run(...). Boot then prepares the environment, creates the application context, loads beans and auto-configuration, refreshes the context, starts the embedded server for web apps, and finally runs startup hooks like ApplicationRunner and CommandLineRunner. If everything succeeds, it publishes an "app is ready" event and returns the running context.
Interview-Ready Answer: "I start with main() calling SpringApplication.run(). Spring Boot first prepares the Environment from config files, profiles, and command-line args, then creates the right ApplicationContext, scans and registers beans, applies auto-configuration, and refreshes the context so singletons get created. In a web app, the embedded server starts during that refresh. After the context is up, Boot runs ApplicationRunner and CommandLineRunner, and if none of those fail, it publishes ApplicationReadyEvent. A detail I like to mention is that in Boot 3, auto-configuration metadata comes from META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, not the old Boot 2 spring.factories path."
Startup flow is the ordered path from a plain Java process to a live Spring app. The key idea is that Boot does not just "start a server"; it builds a full dependency graph, wires beans, applies defaults, and only then marks the app as ready.
main() calls SpringApplication.run(). This is the entry point. Boot now owns the rest of the bootstrap process.Environment is prepared. The Environment is the object that holds properties. Boot loads application.properties or application.yml, profile-specific files like application-prod.yml, system properties, environment variables, and command-line arguments, then merges them by precedence.ApplicationContext is created. For a servlet app this is usually a ServletWebServerApplicationContext; for a reactive app it is a reactive web context; for a non-web app it is a plain application context. The context is the container that stores bean definitions and bean instances.@SpringBootApplication class includes @ComponentScan and @EnableAutoConfiguration. Component scanning finds your beans; auto-configuration adds sensible defaults only when the classpath and existing beans match conditions such as @ConditionalOnClass or @ConditionalOnMissingBean.refresh() is the big moment. Spring creates bean factories, runs bean factory post-processors, instantiates singleton beans, performs dependency injection, calls lifecycle callbacks like @PostConstruct, and resolves most wiring problems here. If a required bean is missing or a constructor throws, startup fails now.ApplicationRunner and CommandLineRunner. These are good for small startup tasks like validation, warmup, or logging, but not for long blocking work.ApplicationReadyEvent is published. At this point the app is considered ready to serve traffic, and SpringApplication.run() returns the live context.| Aspect | ApplicationRunner | CommandLineRunner |
|---|---|---|
| Arguments | Parsed ApplicationArguments | Raw String[] |
| When useful | Need option names | Need simple raw args |
| Typical use | Startup validation | Quick boot logic |
Boot 2 vs Boot 3 note: older Boot 2 auto-configuration metadata came from spring.factories; Boot 3 uses the dedicated AutoConfiguration.imports file. The startup idea is the same, but the discovery mechanism changed.
You want configuration first, beans second, server third, and business hooks last. If you try to do heavy work too early, the container is not ready; if you do it too late, you delay readiness and make deployments slow. The flow exists to keep startup deterministic and failure visible.
Startup cost is roughly proportional to the number of beans and the amount of classpath scanning, so think of it as O(B + C) in practice, where B is bean count and C is metadata scanning. Small services often start in a few hundred milliseconds to a couple of seconds; large enterprise apps with many starters can take 5–15 seconds or more. Lazy initialization can reduce startup time, but it pushes failures to first request time.
@PostConstruct, or bean factory method throws, refresh fails immediately.@Lazy, it may not be created during startup at all.ApplicationReadyEvent, but after the core context refresh begins.Memory hook: "Passport, gate, boarding": first Boot checks the passport (Environment), then opens the gate (refresh()), then boards passengers (Runners).
Real-World Story: Imagine a checkout service in an e-commerce platform. On startup it loads payment config, connects to the database, warms a small cache, and then becomes ready for traffic. A developer once put a remote call to a flaky pricing service inside a CommandLineRunner, because it "felt like startup work." In Kubernetes, every pod spent 20–30 seconds waiting, readiness probes timed out, and the deployment never became healthy. The logs showed the app had created the context, then ended with Application run failed when the runner threw. Customers saw 503 errors because no pod ever reached ready state.
The fix was to keep startup fast, move the expensive call to an async background job after ApplicationReadyEvent, and add a timeout plus fallback. That change made the service start in under 2 seconds again and stopped the rollout failures.
What went wrong technically: the team confused "context is built" with "the app is safe to block on network calls." Startup hooks are still part of the critical path, so a slow or failing dependency can break deployment even if the code looks harmless.
package com.example.startupflow;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
@SpringBootApplication
public class StartupFlowApplication {
private static final Logger log = LoggerFactory.getLogger(StartupFlowApplication.class);
public static void main(String[] args) {
// The only thing main does is hand control to Spring Boot.
// From here on, Boot prepares the environment, context, beans, and runners.
SpringApplication.run(StartupFlowApplication.class, args);
}
@Bean
ApplicationRunner applicationRunner(Environment environment) {
return (ApplicationArguments args) -> {
log.info("ApplicationRunner: option names = {}", args.getOptionNames());
// Edge case: this failure happens after the context has mostly started.
// It demonstrates that startup can still fail late, even after bean creation.
boolean fail = environment.getProperty("demo.fail", Boolean.class, false);
if (fail) {
throw new IllegalStateException("demo.fail=true: refusing to complete startup");
}
};
}
@Bean
CommandLineRunner commandLineRunner() {
return args -> log.info("CommandLineRunner: raw args = {}", Arrays.toString(args));
}
@EventListener
public void onStarted(ApplicationStartedEvent event) {
log.info("ApplicationStartedEvent: context exists, but runners have not finished yet");
}
@EventListener
public void onReady(ApplicationReadyEvent event) {
log.info("ApplicationReadyEvent: the app is fully ready to serve traffic");
}
}
Follow-up & Tricky Questions:
@PostConstruct, ApplicationRunner, or ApplicationReadyEvent? @PostConstruct runs during bean creation inside context refresh, then the runners execute, and only after that does ApplicationReadyEvent fire.@SpringBootApplication matter in startup flow? It bundles component scanning and auto-configuration, so it is the main reason Boot can discover your beans and apply defaults automatically.ApplicationReadyEvent mean the app can never fail again? No. It only means startup finished successfully; the app can still fail later because of runtime exceptions, dead dependencies, or resource exhaustion.CommandLineRunner safer than ApplicationRunner? No. They both run after context refresh and can both abort startup; the real difference is how they receive arguments, not when they run.@Lazy bean is skipped until something asks for it, which can hide startup failures until the first request.Common Mistakes:
main()." Correction: main() only hands control to SpringApplication.run(); the container creates beans later during refresh.Memory Hook: Think "passport, gate, boarding": Boot checks the Environment, opens the container gate with refresh, then boards the runners.
Cheat Sheet:
main() calls SpringApplication.run().Environment first.ApplicationContext.ApplicationRunner and CommandLineRunner run after startup, then ApplicationReadyEvent fires.Practice Tasks:
ApplicationStartedEvent and ApplicationReadyEvent.demo.fail=true and observe how a runner can abort startup.