Spring Boot – Handling Tasks at Server Startup with Runners

Share

Sometimes, we need to perform some tasks at server startup. In this article, we’ll see how we can use the ApplicationRunner or the CommandLineRunner interfaces from Spring Boot to achieve the same.

The ApplicationRunner and The CommandLineRunner Interfaces

Spring Boot has introduced the ApplicationRunner and the CommandLineRunner interfaces in version 1.3.0. We can use either of these interfaces to run some specific code after the SpringApplication starts. Further, both interfaces are functional interfaces with a run method which Spring Boot calls just before SpringApplication.run(…) completes.

Before Spring Boot Runners

Interestingly, before Spring Boot introduced the runner interfaces, the common practice in a Spring-based application was using the InitializingBean interface or (lately) the PostConstruct annotation. However, these two are specific to the bean they are part of. What I mean is that these are called after a bean is initialized. Therefore, these serve the purpose of executing a code before server startup. Yet, the Spring Boot runners are more appropriate for such use cases. Let’s see why!

Let’s Think of an Example

More often than not, I encounter scenarios where I must do something right after the application starts. For example, a few common use cases are initializing the application cache, any other custom initialization, validation, or logging some information when the server starts. 

In this example, let’s consider a simple use case of initializing the cache when the application starts. Don’t worry, to keep it simple, we won’t implement any caching-related stuff here. Instead, we’ll use logs to understand how the Spring Boot runners help us with this.

Spring Boot Runners in Action

So, without much further ado, let’s write some code to use the ApplicationRunner and the CommandLineRunner.

To start with, let’s create a simple Spring Boot application using the Spring Initializr. Obviously, we can add the Spring Web dependency though it is not mandatory.

Next, let’s download and import this project into the IDE of our choice. I use IntelliJ Idea.

ApplicationRunner

The CacheLoaderApplicationRunner Class

So, as discussed, we want to initialize the cache in our application when the server starts. Moreover, we will first use the ApplicationRunner interface for that purpose.

Therefore, we’ll create a class, CacheLoaderApplicationRunner, which implements the ApplicationRunner interface and overrides the run method. Notably, for this class to work, we must register it with the Spring framework as a bean. So, we’ll use the @Component annotation for this purpose.

@Component
public class CacheLoaderApplicationRunner implements ApplicationRunner {

Logger logger = LoggerFactory.getLogger(CacheLoaderApplicationRunner.class);

@Override
public void run(ApplicationArguments args) throws Exception {
if (Objects.isNull(args.getOptionValues("cacheSize"))) {
throw new IllegalArgumentException("cacheSize argument is not specified");
}
String cacheSize = args.getOptionValues("cacheSize").getFirst();
logger.info("------- Inside application runner. Cache Size = {}", cacheSize);
// ... code to load the cache
}
}

Another important thing to note is that the run method takes an ApplicationArguments object as an input parameter. This helps us to make use of the program arguments in our code.

In this case, as you can see, the code is expecting an argument named cacheSize which we can use to decide the size of the cache we want to initialize.

ApplicationRunner in Action

Before running the application, we’ll add a program argument –cacheSize=1000 to the Spring Boot run configuration.

Now, let’s run the application and verify the logs. Here, we can see that the log from the run method is coming just after the server starts:

o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
c.t.spring.SpringBootRunnersApplication : Started SpringBootRunnersApplication in 1.881 seconds (process running for 2.265)
c.t.spring.CacheLoaderApplicationRunner : ------- Inside application runner. Cache Size = 1000
The CacheLoaderCommandLineRunner Class

Next, let’s create the CacheLoaderCommandLineRunner class, which implements the CommandLineRunner interface.

 

@Component
public class CacheLoaderCommandLineRunner implements CommandLineRunner {

Logger logger = LoggerFactory.getLogger(CacheLoaderCommandLineRunner.class);

@Override
public void run(String... args) {
logger.info("------- Inside command line runner. Argument {}", args[0]);
// extract cacheSize value from the string argument
// ... code to load the cache
}
}

Here, we can notice the difference in the input parameter of the run method. We’ll talk about it more in the next section.

CommandLineRunner in Action

Now, let’s use the same run configuration and run the application. Clearly, we can see that the CommandLineRunner works similarly to the ApplicationRunner.

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
c.t.spring.SpringBootRunnersApplication  : Started SpringBootRunnersApplication in 1.823 seconds (process running for 2.207)
c.t.spring.CacheLoaderCommandLineRunner  : ------- Inside command line runner. Argument --cacheSize=1000
c.t.spring.CacheLoaderApplicationRunner  : ------- Inside application runner. Cache Size = 1000
Difference Between ApplicationRunner and CommandLineRunner

As we discussed at the start of the article, both the ApplicationRunner and CommandLineRunner interfaces do the same thing. Moreover, it is also clear from the above example, that the only difference between the two interfaces is the input argument of the run methods

I prefer using ApplicationRunner. The reason is that the object of type ApplicationArguments makes working with the program arguments easier

Furthermore, this example shows that the program argument “--cacheSize=1000″ is passed as a String in the CacheLoaderCommandLineRunner class. Consequently, to use it we must extract the value of cacheSize in the code to work with it. 

On the other hand, in the case of ApplicationRunner, we have used the getOptionsValues method to conveniently read the value of the argument. Furthermore, the ApplicationArguments interface has more such useful methods e.g. containsOption.

Ordering Multiple Runners

Finally, if we’re using multiple runners in the application, we may want to decide the order in which the runners should execute. To do this, we use @Order annotation on the runner class to define the order. By default, the runners execute in random order. 

So, in our example, if we want the CacheLoaderApplicationRunner to run first and then the CacheLoaderCommandLineRunner, all we need to do is define the order like below:

@Component
@Order(1)
public class CacheLoaderApplicationRunner implements ApplicationRunner
@Component
@Order(2)
public class CacheLoaderCommandLineRunner implements CommandLineRunner
Conclusion

To conclude, in this article, we’ve discussed the ApplicationRunner and the CommandLineRunner interfaces with the help of an example.

We’ve considered a use case of initializing a cache when the application starts. However, to keep the example simple we did not code anything related to caching.

Moreover, we’ve also talked about the difference between the two interfaces and how to order the execution of multiple runners.


Share

4 thoughts on “Spring Boot – Handling Tasks at Server Startup with Runners”

  1. Nice usecase Kamlesh.. You can autowire Spring Beans and use them in your CommandLineRunner

    We used it in case of KafkaConsumer semi integration test case. Autowiring the Consumer and call the KafkaListener as a method call in CommandLineRunner Bean

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top