9. Runner Interfaces & Startup Pipeline

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 Logic

Spring Boot provides two specialized runner interfaces:

  • CommandLineRunner
  • ApplicationRunner

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:

  1. Breaks IoC Paradigm: Forces the main() method to act as a manual service locator.
  2. Coupling: Blurs the boundary between application bootstrapping and business execution.
  3. 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 Automatically

3. 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=fast

args 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.txt

ApplicationArguments parses this into:

  • Option Arguments: provider = ["Razorpay"], retry = ["3"]
  • Non-Option Arguments: ["file1.txt"]

5. Comparison & Decision Rules

Feature MetricCommandLineRunnerApplicationRunner
Execution TimingPost-container readinessPost-container readiness
Spring DI SupportFull Constructor InjectionFull Constructor Injection
Argument ParameterString... argsApplicationArguments args
Argument FormatUnparsed raw stringsStructured option parsing
Option Flag AccessManual string splitting requiredargs.getOptionValues("key")
Best FitSimple CLI tasks without optionsStructured CLI tasks with --key=value flags

❓ Knowledge Check

Knowledge Check

What is the primary difference between CommandLineRunner and ApplicationRunner in Spring Boot?

Knowledge Check

Why is using a Runner interface preferred over context.getBean() in main()?

On this page