In short, a JVM with Spring Boot 2.5.x project doesn’t shutdown on application start failure if log4j2’s Async appender is used.

A demo project: sainaen/spring-boot-demo-log4j2 (see commits there for two preconditions: using log4j2 and breaking the app so it won’t start properly).

Example execution
❱ ./gradlew bootJar
BUILD SUCCESSFUL in 1s
4 actionable tasks: 4 executed

❱ java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
2021-07-14T12:12:14,237 [Thread ID=1] ERROR o.s.b.d.LoggingFailureAnalysisReporter -

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to bind properties under 'spring.main.banner-mode' to org.springframework.boot.Banner$Mode:

    Property: spring.main.banner-mode
    Value: break
    Origin: class path resource [application.yml] from demo-0.0.1-SNAPSHOT.jar - 3:21
    Reason: failed to convert java.lang.String to org.springframework.boot.Banner$Mode (caused by java.lang.IllegalArgumentException: No enum constant org.springframework.boot.Banner.Mode.break)

Action:

Update your application's configuration. The following values are valid:

    CONSOLE
    LOG
    OFF
At this point JVM keeps running, waiting for `AsyncAppenderEventDispatcher` thread indefinitely.

I’ve first found this behavior with the application that failed to start due to unreachable MySQL host configured in its DataSource, so it’s not specific to the broken config values.

The same setup works as expected in 2.4.8.

Comment From: wilkinsona

Thanks for the sample. I've reproduced the problem but I'm not entirely sure what's going on just yet. An equivalent app written in Java does not hang. I've tried with ./gradlew bootRun, java -jar, and running the main method in Eclipse. In all three cases, the AsyncAppenderEventDispatcher thread isn't created so it does not prevent the JVM from exiting. I've yet to identify what it is about using Kotlin that causes Log4j2 to behave differently.

Comment From: sainaen

I’m not sure this is related to Kotlin. I’ve just switched to java in a separate branch and it behaves in the same way.

Comment From: wilkinsona

Sorry, I managed to typo the name of the config file when I copied it into the Java-based project. With that file name corrected, I see the hang with Java too.

This appears to be a Log4j2 bug. Using your log4j2.xml configuration, this really simple application will also prevent the JVM for exiting:

package com.example.demo;

import org.slf4j.LoggerFactory;

public class Gh27325Application {

    public static void main(String[] args) {
        LoggerFactory.getLogger(Gh27325Application.class).info("Hello");
    }

}

The problem appears to be that Log4j2 2.14's AsyncAppenderEventDispatcher is not a daemon thread where as it's equivalent in Log4j2 2.13, AsyncThread, is a daemon thread. This has been reported in LOG4J2-3102 where it says that it worked in 2.14.0 and fails with 2.14.1. The fix version on the issue is 2.15.0 which has not yet been released. As it appears to be a regression in 2.14.1, it may be worth asking the Log4j2 team to consider a 2.14.2 that fixes the problem.

Comment From: sainaen

Ohhh, I’ve checked an older Spring Boot version, but didn’t think to check log4j2. :facepalm:

Thank you so much for your help! Sorry for the noise.