Spring Boot Commandline Runner Tutorial

Overview

There are times you need to do some start-up work when your Spring application launch. One of the methods is to use the CommandlineRunner interface.

In this post, I’m going to show you how to use this interface to run startup tasks with flexibility.

Let’s get started.

Meet the CommandlineRunner interface

The CommandlineRunner interface is a functional interface and it has only one method: run.

package org.springframework.boot;

@FunctionalInterface
public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

To implement this interface, you just need to implement the run method.

Use the CommandlineRunner interface in Spring Boot

There are several ways to use this interface in your applications. They all have pros and cons. Let’s discover them one by one.

Make the application class Implement CommandlineRunner

The easiest way is to make the application class implement CommandlineRunner:

@SpringBootApplication
public class CommandlineRunnerApplication implements CommandLineRunner {

   Logger logger = LoggerFactory.getLogger(CommandlineRunnerApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(CommandlineRunnerApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
       logger.info("Hello, world! From commandline runner");
    }
}

As you can see, in the main class, I just implement the interface and write an implementation for the run method.

When starting the app, surely the log message is here:

CommandlineRunner in action
CommandlineRunner in action

While this method is simple to implement, it has some drawbacks:

  • You can not use other beans in the run method
  • You just have one run method, what if you need more?
  • What if you need multiple run scripts and they need to be executed in order?

Well, there are more methods to address such issues.

Use CommandlineRunner as a bean (@Bean)

You can use Spring Dependency Injection mechanism to create a CommandlineRunner bean:

@SpringBootApplication
public class CommandlineRunnerApplication {

   Logger logger = LoggerFactory.getLogger(CommandlineRunnerApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(CommandlineRunnerApplication.class, args);
    }

    @Bean
    CommandLineRunner runner() {
        return args -> {
            logger.info("Hello, world! From commandline runner as a bean");
        };
    }

    @Bean
    CommandLineRunner runner2() {
        return args -> {
            logger.info("Hello, world! From commandline runner ALSO as a bean");
        };
    }
}

As you can see here I am able to create two beans and execute two scripts. All are executed successfully when I start the application.

Creating and running multiple CommandlineRunner beans
Creating and running multiple CommandlineRunner beans

As mentioned above, with this method, I can inject other beans into the runner.

Let’s create a service to use as a dependency:

package com.datmt.commandlinerunner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class DummyService {
    Logger logger = LoggerFactory.getLogger(DummyService.class);

    public void doSomething() {
        logger.info("Hello, world! From DummyService");
    }
}

Now, let’s modify one of the CommandlineRunner beans to use this service as a dependency:

    @Bean
    CommandLineRunner runner2(DummyService dummyService) {
        return args -> dummyService.doSomething();
    }

And the code can execute just fine:

CommandlineRunner with Dependency Injection
CommandlineRunner with Dependency Injection

Now that’s some improvement. I was able to:

  • Remove the implementation of CommandlineRunner in the main application class
  • I can create multiple beans

One issue was not addressed which is I could not run the beans in the order I prefer.

The third method addresses this issue.

CommandlineRunner in components

Instead of creating beans using @Bean, you can Spring stereotypes and implement CommandlineRunner from there.

With this, you can achieve some benefits:

  • Better structured code
  • Multiple runners
  • Order the runners in any way you like

Let’s create two runners using this approach. Let’s consider a scenario where you need to:

  • Create a database connection and
  • Fetch data from a remote URL

Here are the two runners for each task:

package com.datmt.commandlinerunner.runners;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class FetchRemoteURL implements CommandLineRunner {
    Logger logger = LoggerFactory.getLogger(FetchRemoteURL.class);

    @Override
    public void run(String... args) throws Exception {
        logger.info("Remote data fetched");
    }
}
package com.datmt.commandlinerunner.runners;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class CreateDatabaseConnection implements CommandLineRunner {

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

    @Override
    public void run(String... args) throws Exception {
       logger.info("DB connection created");
    }
}

Running the application now would produce the expected result:

Moving CommandlineRunner into components
Moving CommandlineRunner into components

Now, if you want to specify the runner’s order, simply annotate each class with the Order annotation. For example, I would like the FetchRemoteURL runner to run first. Then, I will need to put the Order annotation with a lesser number than that of the Order annotation on the CreateDatabaseConnection class.

@Component
@Order(1)
public class FetchRemoteURL implements CommandLineRunner {
//Code omitted for brevity
}

@Component
@Order(2)
public class CreateDatabaseConnection implements CommandLineRunner {
//Code omitted for brevity
}

Now, if I run the application again, I would see the the “Remote data fetched” log appears first:

CommandlineRunner tasks running in order
CommandlineRunner tasks running in order

Conclusion

The CommandlineRunner interface provides a nice way for developers to create tasks running at the application start. You have some options when it comes to using this interface in your application. If you need a quick and simple implementation, just make the main application class extends the CommandlineRunner interface. If your needs are more sophisticated, consider using the other two.

As usual, the code is available here on Github

Leave a Comment