CommandLineRunner & ApplicationRunner
Sometimes you need code to run once at startup, after the application context is fully initialized but as part of the boot sequence — seeding a database, warming a cache, or printing diagnostics. Spring Boot provides two functional interfaces for exactly this: CommandLineRunner and ApplicationRunner. Any bean implementing either runs automatically just before SpringApplication.run(...) returns.
Why runners?
You could put startup logic in a @PostConstruct method, but that fires while the owning bean is still being created — before the rest of the context is guaranteed ready and before the embedded web server starts. Runners execute after the context is fully refreshed and the server is up, which is the correct moment for “the app is ready, now do X” tasks. They also receive the program’s command-line arguments, which @PostConstruct cannot.
CommandLineRunner
CommandLineRunner hands you the raw arguments as a String[], exactly as the JVM received them.
@Component
public class StartupRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("CommandLineRunner started with " + args.length + " args");
for (String arg : args) {
System.out.println(" arg = " + arg);
}
}
}
ApplicationRunner
ApplicationRunner receives an ApplicationArguments object that has already parsed the raw input into option arguments (--name=value) and non-option arguments (everything else).
@Component
public class DataSeeder implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("Option names: " + args.getOptionNames());
System.out.println("--seed values: " + args.getOptionValues("seed"));
System.out.println("Non-option args: " + args.getNonOptionArgs());
System.out.println("Has --verbose? " + args.containsOption("verbose"));
}
}
Running the app like this:
java -jar app.jar --seed=true import.csv --verbose
Output:
Option names: [seed, verbose]
--seed values: [true]
Non-option args: [import.csv]
Has --verbose? true
getOptionValues returns a List<String> because the same option may appear more than once (--seed=a --seed=b); it returns null if the option is absent.
CommandLineRunner vs ApplicationRunner
Both run at the same point in the lifecycle. The only real difference is the shape of the argument they receive.
| Aspect | CommandLineRunner | ApplicationRunner |
|---|---|---|
| Method signature | run(String... args) | run(ApplicationArguments args) |
| Argument form | Raw String[], unparsed | Parsed into options + non-options |
| Option access | Manual parsing | getOptionNames(), getOptionValues(name) |
| Non-option access | Manual parsing | getNonOptionArgs() |
| Best for | Simple, few or no args | Apps that read --flag=value style args |
Tip: Reach for
ApplicationRunnerwhenever you accept named flags — it saves you from writing fragile string-splitting code.
Ordering multiple runners
When several runners exist, control their sequence with @Order (or by implementing Ordered). Lower numbers run first. Runners without @Order are treated as the lowest precedence and run last.
@Component
@Order(1)
public class FirstRunner implements CommandLineRunner {
public void run(String... args) {
System.out.println("First runner");
}
}
@Component
@Order(2)
public class SecondRunner implements ApplicationRunner {
public void run(ApplicationArguments args) {
System.out.println("Second runner");
}
}
Output:
First runner
Second runner
Note:
CommandLineRunnerandApplicationRunnerbeans share a single ordered list, so an@Order(1)ApplicationRunnerstill runs before an@Order(2)CommandLineRunner.
Lambda style
Because both are functional interfaces, you can declare a runner concisely as a @Bean instead of a full class — useful for small one-off tasks.
@Configuration
public class StartupConfig {
@Bean
CommandLineRunner welcome() {
return args -> System.out.println("Application started successfully");
}
}
Error handling
If a runner throws an exception, SpringApplication.run(...) propagates it and the application fails to start (the JVM exits with a non-zero code). That is usually the desired behavior — you do not want a half-initialized app serving traffic. Wrap recoverable work in try/catch if a failure should not abort startup.
Best Practices
- Use runners for startup-only tasks; for recurring work see scheduling instead.
- Prefer
ApplicationRunnerwhen parsing named command-line options. - Keep runner logic fast; long-running work delays the app becoming ready.
- Make ordering explicit with
@Orderwhen more than one runner exists. - Let unrecoverable startup failures propagate so the app fails fast.