CommandLineRunner vs ApplicationRunner
Explore why Spring Boot runner interfaces are needed, why manually fetching beans from main() is an anti-pattern, raw String... args handling vs structured ApplicationArguments.
CommandLineRunner vs ApplicationRunner
In web applications, execution begins when an HTTP request hits a @RestController. But in batch jobs, data migrations, or background services, execution must begin immediately after the Spring container boots up.
1. Why Do We Need Runner Interfaces?
When an application starts without an active web server or incoming HTTP requests, how does code get executed?
HTTP Web Request Flow:
Client Request ──► Embedded Tomcat ──► DispatcherServlet ──► RestController ──► Business Logic
Non-Web / Startup Flow:
Application Startup ──► Container Ready ──► Startup Runner ──► Business LogicSpring Boot provides two specialized runner interfaces:
CommandLineRunnerApplicationRunner
Both interfaces allow executing post-boot logic after the ApplicationContext is fully initialized, beans are created, and dependencies are injected.
2. Anti-Pattern: Manually Fetching Beans from main()
Technically, a developer could manually trigger startup logic inside main():
// POOR ARCHITECTURE (Manual Bean Fetching in main)
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(Application.class, args);
// Manually retrieving bean and calling method
PaymentService paymentService = context.getBean(PaymentService.class);
paymentService.pay();
}
}Why Manual Bean Retrieval Is Discouraged:
- Breaks IoC Paradigm: Forces the
main()method to act as a manual service locator. - Coupling: Blurs the boundary between application bootstrapping and business execution.
- Container Management: Bypasses Spring's managed lifecycle and exception-handling callbacks.
The Spring-Managed Solution:
Spring Application Starts ──► Context Created ──► Bean Registered ──► Spring Invokes Runner Automatically3. CommandLineRunner: Raw String Arguments
CommandLineRunner accepts raw, unparsed string arguments passed to the application at launch:
@Component
public class BatchAppRunner implements CommandLineRunner {
private final PaymentService paymentService;
// Standard Constructor Injection
public BatchAppRunner(PaymentService paymentService) {
this.paymentService = paymentService;
}
@Override
public void run(String... args) throws Exception {
System.out.println("Executing CommandLineRunner...");
// Processing raw CLI arguments
for (String arg : args) {
System.out.println("Raw Argument: " + arg);
}
paymentService.pay();
}
}If launched from shell:
java -jar app.jar hello world --mode=fastargs receives a raw String[] array: ["hello", "world", "--mode=fast"].
4. ApplicationRunner: Structured ApplicationArguments
If your application takes options (e.g. --provider=Razorpay --retry=3), parsing raw strings manually in CommandLineRunner requires custom string splitters.
ApplicationRunner provides a structured ApplicationArguments wrapper that parses option arguments automatically:
@Component
public class StructuredAppRunner implements ApplicationRunner {
private final PaymentService paymentService;
public StructuredAppRunner(PaymentService paymentService) {
this.paymentService = paymentService;
}
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("Executing ApplicationRunner...");
// Check if option --provider exists
if (args.containsOption("provider")) {
List<String> values = args.getOptionValues("provider");
System.out.println("Parsed Option 'provider': " + values.get(0));
}
// Access non-option raw arguments
List<String> nonOptionArgs = args.getNonOptionArgs();
System.out.println("Non-option arguments: " + nonOptionArgs);
paymentService.pay();
}
}If launched from shell:
java -jar app.jar --provider=Razorpay --retry=3 file1.txtApplicationArguments parses this into:
- Option Arguments:
provider = ["Razorpay"],retry = ["3"] - Non-Option Arguments:
["file1.txt"]
5. Comparison & Decision Rules
| Feature Metric | CommandLineRunner | ApplicationRunner |
|---|---|---|
| Execution Timing | Post-container readiness | Post-container readiness |
| Spring DI Support | Full Constructor Injection | Full Constructor Injection |
| Argument Parameter | String... args | ApplicationArguments args |
| Argument Format | Unparsed raw strings | Structured option parsing |
| Option Flag Access | Manual string splitting required | args.getOptionValues("key") |
| Best Fit | Simple CLI tasks without options | Structured CLI tasks with --key=value flags |
❓ Knowledge Check
What is the primary difference between CommandLineRunner and ApplicationRunner in Spring Boot?
Why is using a Runner interface preferred over context.getBean() in main()?
@Value vs @ConfigurationProperties & Relaxed Binding
Master individual property injection via @Value, default value syntax, grouped configuration binding via @ConfigurationProperties, relaxed binding mechanics, and trade-off comparison matrices.
End-to-End Configuration Pipeline & Pitfalls
Master the complete Spring Boot environment-to-runner execution pipeline, complexity trade-offs, production pitfalls, end-to-end code examples, and self-assessment challenges.