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.