How to solve springboot CommandLineRunner run not called problem?

1. The purpose of this post

When we use CommandLineRunner in springboot applications, we expect its run to be called when the app starts. But , sometimes the run is not called even if the code and the settings is both ok.

This article would help you to debug and solve this issue.

2. Environments

  • SpringBoot 1.x or 2.x
  • Java 1.8+

3. The problem code

3.1 pom.xml

This is the pom.xml content:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-issue1</artifactId>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3.2 Runner1.java and Runner2.java

There are two CommandLineRunners in this project:

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

    @Override
    public void run(String... strings) throws Exception {
        logger.debug("runner1 started");
    }
}

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

    @Override
    public void run(String... strings) throws Exception {
        logger.debug("runner2 started");
    }
}

Now everything works fine:

2019-03-08 11:21:17.395 DEBUG 91632 --- [           main] com.bswen.sbissue1.runners.Runner1       : runner1 started
2019-03-08 11:21:17.395 DEBUG 91632 --- [           main] com.bswen.sbissue1.runners.Runner2       : runner2 started

But after we add a new Runner3.java, runner1 and runner2 stop working.

3.3 Adding Runner3.java to cause the problem

@Component
public class Runner3 implements Runnable {
    private static Logger logger = LoggerFactory.getLogger(Runner3.class);
    Object lock = new Object();

    @Override
    @PostConstruct
    public void run() {
        logger.debug("runner3 started");
        try {
            synchronized (lock) {
                lock.wait();
            }
            logger.debug("runner3 unlocked");
        }catch (Exception ex) {
            logger.error("",ex);
        }
    }
}

In Runner3.java, we setup a lock to cause the current thread to be blocked. Then we can see what happens next:

2019-03-08 11:23:48.206 DEBUG 91645 --- [           main] com.bswen.sbissue1.runners.Runner3       : runner3 started

The runner1 and runner2 call log is not printed, why?

3.4 The reason

All the problem is caused by the @PostConstruct annotation. The Runner3 is a Runnable, but it’s not called as a thread, but as a normal method. And because the springboot starter thread is blocked by Runner3, so runner1 and runner2 can not get the attention.

3.5 How to solve it

Comment out the @PostConstruct, and start it with a Thread, then everything works fine:

@Component
public class Runner3 implements Runnable {
    private static Logger logger = LoggerFactory.getLogger(Runner3.class);
    Object lock = new Object();

    @Override
    //@PostConstruct
    public void run() {
    	....
    }

And start runner3 in Runner2

@Component
public class Runner2 implements CommandLineRunner {
    private static Logger logger = LoggerFactory.getLogger(Runner2.class);
    @Autowired
    private Runner3 runner3;
    @Override
    public void run(String... strings) throws Exception {
        logger.debug("runner2 started");

        (new Thread(runner3)).start();
    }
}

Then we get this:

2019-03-08 11:27:59.796 DEBUG 91655 --- [           main] com.bswen.sbissue1.runners.Runner1       : runner1 started
2019-03-08 11:27:59.796 DEBUG 91655 --- [           main] com.bswen.sbissue1.runners.Runner2       : runner2 started
2019-03-08 11:27:59.797 DEBUG 91655 --- [       Thread-3] com.bswen.sbissue1.runners.Runner3       : runner3 started

The example source code has been uploaded to github, you can visit here to view the example source codes.