How to Run Code When a Spring Boot Application Starts

Introduction

Some work has to happen once, when the application starts: loading reference data, warming a cache, checking that a required directory exists, printing a summary of the configuration. Spring Boot offers several hooks for this, and they run at different moments - some before the other beans exist, some after everything is ready. This quick tip shows which one to use, the order in which they run, and what happens when startup code fails.

Tested on Spring Boot 4.1.2, Spring Framework 7.0.10 and JDK 25.

Running startup code with an ApplicationRunner

For code that should run once the application has started, declare an ApplicationRunner (or CommandLineRunner) bean: Spring Boot calls it after all beans are created, before the application counts as ready, and passes it the command-line arguments. Several runners are ordered with @Order.
The short answer
@Bean
ApplicationRunner loadReferenceData(ReferenceDataService service) {
  return args -> service.load();
}

Which startup hook to use

HookWhen it runsUse it forLimit
@PostConstruct on a bean methodWhile that bean is being createdInitializing the bean itself from its own dependenciesOther beans may not exist yet; the bean's own proxies, such as @Transactional, do not apply to this call
ApplicationRunner / CommandLineRunner beanAfter every bean is created, before the ready eventStartup tasks that use other beans; tasks that read argumentsA failure stops the application
@EventListener(ApplicationReadyEvent.class)Last, after the runnersWork that needs a fully started application, such as announcing that it is readyA failure also stops the application
The two runner interfaces differ only in their argument: ApplicationRunner receives ApplicationArguments, parsed into options such as --mode=full and plain arguments, and CommandLineRunner receives the raw String[].

The order of the startup hooks

The example has one hook of each kind. Each prints a line, and two arguments make a runner or the ready listener fail, so that the effect of a failure can be seen too.
src/main/resources/application.properties
spring.main.banner-mode=off
logging.level.root=WARN
StartupApp.java
//StartupApp.java
package com.javabrahman.tips.startup;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StartupApp {

  public static void main(String[] args) {
    SpringApplication.run(StartupApp.class, args);
    System.out.println("main(): SpringApplication.run() has returned");
  }
}
CacheWarmer.java
//CacheWarmer.java
package com.javabrahman.tips.startup;

import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

@Component
public class CacheWarmer {

  @PostConstruct
  void init() {
    System.out.println("1. @PostConstruct        - CacheWarmer is initialized; other beans may not exist yet");
  }
}
StartupTasks.java
//StartupTasks.java
package com.javabrahman.tips.startup;

import java.util.Arrays;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;

@Configuration
public class StartupTasks {

  @EventListener(ApplicationStartedEvent.class)
  void started() {
    System.out.println("2. ApplicationStartedEvent - the context is refreshed; runners have not run yet");
  }

  @Bean
  @Order(1)
  ApplicationRunner loadReferenceData() {
    return args -> {
      System.out.println("3. ApplicationRunner (@Order 1) - option names: " + args.getOptionNames());
      if (args.containsOption("fail-runner")) {
        throw new IllegalStateException("reference data could not be loaded");
      }
    };
  }

  @Bean
  @Order(2)
  CommandLineRunner printArguments() {
    return args -> System.out.println("4. CommandLineRunner (@Order 2) - raw arguments: " + Arrays.toString(args));
  }

  @EventListener(ApplicationReadyEvent.class)
  void ready(ApplicationReadyEvent event) {
    System.out.println("5. ApplicationReadyEvent  - the application is ready");
    if (event.getArgs().length > 0 && event.getArgs()[0].equals("--fail-ready")) {
      throw new IllegalStateException("ready listener failed");
    }
  }

  @EventListener(ApplicationFailedEvent.class)
  void failed(ApplicationFailedEvent event) {
    System.out.println("X. ApplicationFailedEvent - startup failed: " + event.getException().getMessage());
  }
}
 OUTPUT of java StartupApp --mode=full report.txt
1. @PostConstruct        - CacheWarmer is initialized; other beans may not exist yet
2. ApplicationStartedEvent - the context is refreshed; runners have not run yet
3. ApplicationRunner (@Order 1) - option names: [mode]
4. CommandLineRunner (@Order 2) - raw arguments: [--mode=full, report.txt]
5. ApplicationReadyEvent  - the application is ready
main(): SpringApplication.run() has returned
 OUTPUT with --fail-runner (the printed lines and the error)
1. @PostConstruct        - CacheWarmer is initialized; other beans may not exist yet
2. ApplicationStartedEvent - the context is refreshed; runners have not run yet
3. ApplicationRunner (@Order 1) - option names: [fail-runner]
X. ApplicationFailedEvent - startup failed: reference data could not be loaded
2026-11-13T23:01:18.813+05:30 ERROR 37856 --- [           main] o.s.boot.SpringApplication               : Application run failed
java.lang.IllegalStateException: reference data could not be loaded
 OUTPUT with --fail-ready (the printed lines and the error)
1. @PostConstruct        - CacheWarmer is initialized; other beans may not exist yet
2. ApplicationStartedEvent - the context is refreshed; runners have not run yet
3. ApplicationRunner (@Order 1) - option names: [fail-ready]
4. CommandLineRunner (@Order 2) - raw arguments: [--fail-ready]
5. ApplicationReadyEvent  - the application is ready
2026-11-13T23:01:21.918+05:30 ERROR 60600 --- [           main] o.s.boot.SpringApplication               : Application run failed
java.lang.IllegalStateException: ready listener failed
Explanation of the output
  • The order is always the same: @PostConstruct while the beans are created, then ApplicationStartedEvent, the runners in @Order sequence, and ApplicationReadyEvent last - and only then does SpringApplication.run() return to main().
  • The ApplicationRunner saw the option mode, parsed from --mode=full; the CommandLineRunner saw both arguments as they were typed, including report.txt.
  • A failing runner - the exception from runner 3 ended the startup: runner 4 and the ready event never ran, ApplicationFailedEvent was published, Spring Boot logged "Application run failed", and run() did not return.
  • A failing ready listener - every runner had run, but the exception still made the run fail with "Application run failed". The ApplicationFailedEvent listener was not called in this case.

Pitfalls

  • Runners run on the main thread, one after the other, and the application is not ready until they return. A slow task delays readiness, which matters when a platform waits for the readiness probe; start long work on another thread, or use @Async.
  • An exception in a runner stops the whole application. Catch and log exceptions from tasks that are allowed to fail.
  • @PostConstruct is called on the bean itself, not through its proxy: a @Transactional method called from it runs without a transaction.
  • Runners also run in tests that start the application context, such as @SpringBootTest. Guard tasks that must not run there with a profile or a property.

Related Spring Boot tutorials

@PostConstruct and the rest of a bean's lifecycle are covered in bean lifecycle: @PostConstruct, @PreDestroy and BeanPostProcessorClick to read tutorial: Spring Bean Lifecycle: @PostConstruct, @PreDestroy and More, and Spring's events, including the application events above, in application events with @EventListenerClick to read tutorial: Application Events in Spring with @EventListener.