Skip to content

Using log4j-to-slf4j in a web application incorrectly sets a value in a ThreadLocal in org.apache.logging.slf4j.SLF4JLogger #3819

@gmiscione

Description

@gmiscione

Description

We have a web application packaged as a .war file deployed in Tomcat 11. The web application uses logback as the main logger and slf4j as a facade. Then we use log4j-to-slf4j as a bridge to redirect logs from dependencies using log4j2 to the logback logger. When we undeploy the web application or when we stop Tomcat, we see these line in the catalina.out file:

11-Jul-2025 15:29:48.796 SEVERE [Catalina-utility-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [<app name>] created a ThreadLocal with key of type [java.lang.ThreadLocal.SuppliedThreadLocal] (value [java.lang.ThreadLocal$SuppliedThreadLocal@22437c61]) and a value of type [org.apache.logging.slf4j.SLF4JLogBuilder] (value [org.apache.logging.slf4j.SLF4JLogBuilder@2be61ed4]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

We investigated the issue and we found that in method org.apache.logging.slf4j.SLF4JLogger#getLogBuilder there is this code:

  protected LogBuilder getLogBuilder(final Level level) {
      final SLF4JLogBuilder builder = logBuilder.get();
      return Constants.ENABLE_THREADLOCALS && !builder.isInUse()
              ? builder.reset(this, level)
              : new SLF4JLogBuilder(this, level);
  }

Constants.ENABLE_THREADLOCALS is false since the code correctly detects that it is running in a web application, but still the line final SLF4JLogBuilder builder = logBuilder.get(); stores the initial value into the ThreadLocal. That value is then ignored and is never cleared.

The value of Constants.ENABLE_THREADLOCALS should be checked before logBuilder.get() is even called.

Configuration

Version: 2.25.0

Operating system: Any + Tomcat 11.0.6

JDK: 21.0.7

Logs

From catalina.out file:

11-Jul-2025 15:29:48.796 SEVERE [Catalina-utility-5] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [<app name>] created a ThreadLocal with key of type [java.lang.ThreadLocal.SuppliedThreadLocal] (value [java.lang.ThreadLocal$SuppliedThreadLocal@22437c61]) and a value of type [org.apache.logging.slf4j.SLF4JLogBuilder] (value [org.apache.logging.slf4j.SLF4JLogBuilder@2be61ed4]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

Reproduction

  1. Create a web application using log4j-to-slf4j, slf4j and logback;
  2. Deploy the web application in tomcat 11.0.6 and execute code that uses a log4j2 logger to write something;
  3. Undeploy the web application and check the catalina.out file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIncorrect, unexpected, or unintended behavior of existing code

    Type

    Projects

    Status

    Ready

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions