You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2024/01/10 08:59:57 UTC

(logging-log4j2) branch main updated: Remove `log4j2.enable.threadlocals` property (#2171)

This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/main by this push:
     new 0d6876d11e Remove `log4j2.enable.threadlocals` property (#2171)
0d6876d11e is described below

commit 0d6876d11e4089d5e12b4a334ead59f6ca624b0c
Author: Volkan Yazıcı <vo...@yazi.ci>
AuthorDate: Wed Jan 10 09:59:49 2024 +0100

    Remove `log4j2.enable.threadlocals` property (#2171)
---
 .../apache/log4j/layout/Log4j1SyslogLayout.java    |   3 +-
 .../apache/logging/log4j/AbstractLoggerTest.java   |   6 +-
 .../org/apache/logging/log4j/LogManagerTest.java   |  11 +-
 .../java/org/apache/logging/log4j/LoggerTest.java  |  24 +-
 .../internal/recycler/RecyclerFactoryTestUtil.java |  11 +-
 .../ThreadLocalRecyclerFactoryProviderTest.java    |   6 +-
 .../log4j/spi/DefaultThreadContextMapTest.java     | 225 ----------------
 .../util/LegacyPropertiesCompatibilityTest.java    |   1 -
 .../logging/log4j/util/Unbox2ConfigurableTest.java |   5 -
 .../org/apache/logging/log4j/ThreadContext.java    |   8 -
 .../ThreadLocalRecyclerFactoryProvider.java        |   7 +-
 .../logging/log4j/message/MessageFactory.java      |  13 -
 .../logging/log4j/message/MessageFactory2.java     |   1 -
 .../log4j/message/ParameterizedMessageFactory.java | 191 --------------
 .../log4j/message/ReusableMessageFactory.java      |  10 +-
 .../logging/log4j/spi/DefaultThreadContextMap.java | 228 -----------------
 .../apache/logging/log4j/spi/LoggingSystem.java    |  16 +-
 .../logging/log4j/spi/LoggingSystemProperty.java   |  12 +-
 .../logging/log4j/spi/PropertyComponent.java       |   2 -
 .../org/apache/logging/log4j/util/Constants.java   |  22 --
 .../java/org/apache/logging/log4j/util/Unbox.java  |  29 +--
 .../src/main/resources/log4j2.propertyMapping.json |   2 -
 .../core/async/perftest/ResponseTimeTest.java      |   5 -
 .../log4j/core/async/perftest/SimplePerfTest.java  |  16 --
 .../org/apache/logging/log4j/core/LoggerTest.java  |   4 +-
 .../appender/rolling/PatternProcessorTest.java     |  55 ++--
 .../RollingRandomAccessFileManagerTest.java        |   4 +
 .../async/AsyncLoggerThreadNameStrategyTest.java   |  78 ------
 .../log4j/core/async/AsyncThreadContextTest.java   |   2 -
 ...llAsyncLoggerConfigLoggingFromToStringTest.java |   2 -
 .../core/impl/ThreadContextDataInjectorTest.java   |  11 +-
 ...TestBase.java => DatePatternConverterTest.java} |  78 +++---
 .../DatePatternConverterWithThreadLocalsTest.java  |  31 ---
 ...atePatternConverterWithoutThreadLocalsTest.java |  28 --
 .../src/test/resources/perf/SimplePerfTest.sh      |   1 -
 .../src/test/resources/perf/runResponseTm.sh       |   1 -
 .../appender/rolling/DefaultRolloverStrategy.java  |   5 +-
 .../rolling/DirectWriteRolloverStrategy.java       |  11 +-
 .../core/appender/rolling/PatternProcessor.java    |   9 +-
 .../core/appender/rolling/RollingFileManager.java  |   7 +-
 .../rolling/RollingRandomAccessFileManager.java    |   6 +-
 .../logging/log4j/core/async/AsyncLogger.java      | 283 ++-------------------
 .../log4j/core/async/AsyncLoggerContext.java       |   8 -
 .../log4j/core/async/AsyncLoggerDisruptor.java     |  89 +------
 .../logging/log4j/core/async/DisruptorUtil.java    |   6 +-
 .../log4j/core/async/RingBufferLogEvent.java       |  16 +-
 .../core/async/ThreadNameCachingStrategy.java      |  95 -------
 .../core/impl/ContextDataInjectorFactory.java      |   3 +-
 .../logging/log4j/core/impl/DefaultBundle.java     |  10 +-
 .../log4j/core/impl/ReusableLogEventFactory.java   |   9 +-
 .../logging/log4j/core/layout/HtmlLayout.java      |   2 +-
 .../log4j/core/pattern/DatePatternConverter.java   | 140 +++-------
 .../core/pattern/FileDatePatternConverter.java     |  11 +-
 .../log4j/gctests/GcFreeLoggingTestUtil.java       |   3 -
 .../gctests/JsonTemplateLayoutGcFreeTest.java      |   1 -
 .../resources/gcFreeJsonTemplateLayoutLogging.xml  |   2 +-
 .../template/json/JsonTemplateLayoutTest.java      |  13 +-
 .../perf/jmh/AsyncLoggersLocationBenchmark.java    |   1 -
 .../log4j/perf/jmh/ThreadContextBenchmark.java     |   3 -
 .../ThreadLocalVsConcurrentHashMapBenchmark.java   |  92 -------
 .../log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java | 200 ---------------
 .../java/org/apache/logging/slf4j/LoggerTest.java  |   4 +-
 .../.3.x.x/remove_thread_locals_property.xml       |   8 +
 src/site/_release-notes/_3.x.x.adoc                |   3 +
 src/site/asciidoc/manual/extending.adoc            |   3 +-
 src/site/asciidoc/manual/systemproperties.adoc     |   4 +-
 66 files changed, 241 insertions(+), 1955 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
index 03ee98a514..1e15c300c3 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/layout/Log4j1SyslogLayout.java
@@ -146,7 +146,7 @@ public final class Log4j1SyslogLayout extends AbstractStringLayout {
      */
     private static final String[] dateFormatOptions = {"MMM dd HH:mm:ss", null, "en"};
 
-    private final LogEventPatternConverter dateConverter = DatePatternConverter.newInstance(dateFormatOptions);
+    private final LogEventPatternConverter dateConverter;
 
     private Log4j1SyslogLayout(
             final Configuration config,
@@ -160,6 +160,7 @@ public final class Log4j1SyslogLayout extends AbstractStringLayout {
         this.facilityPrinting = facilityPrinting;
         this.header = header;
         this.messageLayout = messageLayout;
+        this.dateConverter = DatePatternConverter.newInstance(config, dateFormatOptions);
     }
 
     /**
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
index 5cd1119a41..0f1d4f0078 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
@@ -28,7 +28,7 @@ import java.util.List;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ObjectMessage;
 import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.spi.AbstractLogger;
 import org.apache.logging.log4j.status.StatusData;
@@ -964,7 +964,7 @@ public class AbstractLoggerTest {
         private int objectCount;
 
         CountingLogger() {
-            super("CountingLogger", ParameterizedMessageFactory.INSTANCE);
+            super("CountingLogger", new ReusableMessageFactory());
         }
 
         void setCurrentLevel(final Level currentLevel) {
@@ -1261,7 +1261,7 @@ public class AbstractLoggerTest {
         private final boolean expectingThrowables;
 
         ThrowableExpectingLogger(final boolean expectingThrowables) {
-            super("ThrowableExpectingLogger", ParameterizedMessageFactory.INSTANCE);
+            super("ThrowableExpectingLogger", new ReusableMessageFactory());
             this.expectingThrowables = expectingThrowables;
         }
 
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java
index 111b6c77cc..2e767bc7cf 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LogManagerTest.java
@@ -21,7 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import java.io.Closeable;
 import java.io.IOException;
-import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.junit.jupiter.api.Test;
 import org.junitpioneer.jupiter.ReadsSystemProperty;
@@ -52,25 +52,26 @@ public class LogManagerTest {
         Logger logger = LogManager.getLogger();
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
-        logger = LogManager.getLogger(ParameterizedMessageFactory.INSTANCE);
+        final ReusableMessageFactory messageFactory = new ReusableMessageFactory();
+        logger = LogManager.getLogger(messageFactory);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
         logger = LogManager.getLogger((Class<?>) null);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
-        logger = LogManager.getLogger((Class<?>) null, ParameterizedMessageFactory.INSTANCE);
+        logger = LogManager.getLogger((Class<?>) null, messageFactory);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
         logger = LogManager.getLogger((String) null);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
-        logger = LogManager.getLogger((String) null, ParameterizedMessageFactory.INSTANCE);
+        logger = LogManager.getLogger((String) null, messageFactory);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
         logger = LogManager.getLogger((Object) null);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
-        logger = LogManager.getLogger((Object) null, ParameterizedMessageFactory.INSTANCE);
+        logger = LogManager.getLogger((Object) null, messageFactory);
         assertNotNull(logger, "No Logger returned");
         assertEquals(LogManagerTest.class.getName(), logger.getName(), "Incorrect Logger name: " + logger.getName());
     }
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java
index c1cb6d7751..7f4d6b8b37 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/LoggerTest.java
@@ -34,7 +34,7 @@ import org.apache.logging.log4j.message.JsonMessage;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.ObjectMessage;
-import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.SimpleMessageFactory;
 import org.apache.logging.log4j.message.StringFormatterMessageFactory;
@@ -61,7 +61,7 @@ public class LoggerTest {
     public static final LoggerContextFactoryExtension EXTENSION =
             new LoggerContextFactoryExtension(new TestLoggerContextFactory());
 
-    private static class TestParameterizedMessageFactory {
+    private static class TestReusableMessageFactory {
         // empty
     }
 
@@ -344,12 +344,12 @@ public class LoggerTest {
 
     @Test
     @ResourceLock("log4j2.TestLogger")
-    public void getLogger_Class_ParameterizedMessageFactory() {
+    public void getLogger_Class_ReusableMessageFactory() {
         // The TestLogger logger was already created in an instance variable for this class.
         // The message factory is only used when the logger is created.
-        final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE;
+        final MessageFactory messageFactory = new ReusableMessageFactory();
         final TestLogger testLogger =
-                (TestLogger) LogManager.getLogger(TestParameterizedMessageFactory.class, messageFactory);
+                (TestLogger) LogManager.getLogger(TestReusableMessageFactory.class, messageFactory);
         assertNotNull(testLogger);
         assertEqualMessageFactory(messageFactory, testLogger);
         testLogger.debug("{}", Integer.MAX_VALUE);
@@ -375,12 +375,12 @@ public class LoggerTest {
 
     @Test
     @ResourceLock("log4j2.TestLogger")
-    public void getLogger_Object_ParameterizedMessageFactory() {
+    public void getLogger_Object_ReusableMessageFactory() {
         // The TestLogger logger was already created in an instance variable for this class.
         // The message factory is only used when the logger is created.
-        final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE;
+        final MessageFactory messageFactory = new ReusableMessageFactory();
         final TestLogger testLogger =
-                (TestLogger) LogManager.getLogger(new TestParameterizedMessageFactory(), messageFactory);
+                (TestLogger) LogManager.getLogger(new TestReusableMessageFactory(), messageFactory);
         assertNotNull(testLogger);
         assertEqualMessageFactory(messageFactory, testLogger);
         testLogger.debug("{}", Integer.MAX_VALUE);
@@ -419,7 +419,7 @@ public class LoggerTest {
         assertNotNull(testLogger);
         assertEqualMessageFactory(messageFactory, testLogger);
         final TestLogger testLogger2 = (TestLogger)
-                LogManager.getLogger("getLogger_String_MessageFactoryMismatch", ParameterizedMessageFactory.INSTANCE);
+                LogManager.getLogger("getLogger_String_MessageFactoryMismatch", new ReusableMessageFactory());
         assertNotNull(testLogger2);
         // TODO: How to test?
         // This test context always creates new loggers, other test context impls I tried fail other tests.
@@ -433,10 +433,10 @@ public class LoggerTest {
 
     @Test
     @ResourceLock("log4j2.TestLogger")
-    public void getLogger_String_ParameterizedMessageFactory() {
-        final ParameterizedMessageFactory messageFactory = ParameterizedMessageFactory.INSTANCE;
+    public void getLogger_String_ReusableMessageFactory() {
+        final MessageFactory messageFactory = new ReusableMessageFactory();
         final TestLogger testLogger =
-                (TestLogger) LogManager.getLogger("getLogger_String_ParameterizedMessageFactory", messageFactory);
+                (TestLogger) LogManager.getLogger("getLogger_String_ReusableMessageFactory", messageFactory);
         assertNotNull(testLogger);
         assertEqualMessageFactory(messageFactory, testLogger);
         testLogger.debug("{}", Integer.MAX_VALUE);
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java
index a6c8f99a2c..85ec0c3cdb 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/RecyclerFactoryTestUtil.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.internal.recycler;
 
 import edu.umd.cs.findbugs.annotations.Nullable;
 import java.util.Properties;
+import org.apache.logging.log4j.spi.LoggingSystemProperty;
 import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
 import org.apache.logging.log4j.util.PropertiesUtil;
@@ -29,16 +30,16 @@ final class RecyclerFactoryTestUtil {
 
     @Nullable
     static RecyclerFactory createForEnvironment(
-            @Nullable Boolean threadLocalsEnabled, @Nullable final String factory, @Nullable final Integer capacity) {
+            @Nullable Boolean webApp, @Nullable final String factory, @Nullable final Integer capacity) {
         final Properties properties = new Properties();
-        if (threadLocalsEnabled != null) {
-            properties.setProperty("log4j2.*.ThreadLocals.enable", "" + threadLocalsEnabled);
+        if (webApp != null) {
+            properties.setProperty(LoggingSystemProperty.IS_WEBAPP.getSystemKey(), webApp + "");
         }
         if (factory != null) {
-            properties.setProperty("log4j2.*.Recycler.factory", factory);
+            properties.setProperty(LoggingSystemProperty.RECYCLER_FACTORY.getSystemKey(), factory);
         }
         if (capacity != null) {
-            properties.setProperty("log4j2.*.Recycler.capacity", "" + capacity);
+            properties.setProperty(LoggingSystemProperty.RECYCLER_CAPACITY.getSystemKey(), capacity + "");
         }
         final PropertyEnvironment env = new PropertiesUtil(properties);
         return RecyclerFactoryRegistry.findRecyclerFactory(env);
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
index 147a84e52c..2d2bdf3710 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProviderTest.java
@@ -44,7 +44,7 @@ class ThreadLocalRecyclerFactoryProviderTest {
 
     @BeforeEach
     void setUp() {
-        final RecyclerFactory recyclerFactory = createForEnvironment(null, "threadLocal", CAPACITY);
+        final RecyclerFactory recyclerFactory = createForEnvironment(false, "threadLocal", CAPACITY);
         assertThat(recyclerFactory).isInstanceOf(ThreadLocalRecyclerFactory.class);
         assert recyclerFactory != null;
         recycler = (ThreadLocalRecycler<RecyclableObject>) recyclerFactory.create(RecyclableObject::new);
@@ -52,8 +52,8 @@ class ThreadLocalRecyclerFactoryProviderTest {
     }
 
     @Test
-    void should_not_be_configured_when_TLs_are_disabled() {
-        assertThatThrownBy(() -> createForEnvironment(false, "threadLocal", null))
+    void should_not_be_configured_for_webapps() {
+        assertThatThrownBy(() -> createForEnvironment(true, "threadLocal", null))
                 .isInstanceOf(IllegalArgumentException.class)
                 .hasMessageStartingWith("failed to configure recycler");
     }
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
deleted file mode 100644
index 7fcb329afd..0000000000
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/DefaultThreadContextMapTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.spi;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.util.HashMap;
-import java.util.Map;
-import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests the {@code DefaultThreadContextMap} class.
- */
-@UsingThreadContextMap
-public class DefaultThreadContextMapTest {
-
-    @Test
-    public void testEqualsVsSameKind() {
-        final DefaultThreadContextMap map1 = createMap();
-        final DefaultThreadContextMap map2 = createMap();
-        assertEquals(map1, map1);
-        assertEquals(map2, map2);
-        assertEquals(map1, map2);
-        assertEquals(map2, map1);
-    }
-
-    @Test
-    public void testHashCodeVsSameKind() {
-        final DefaultThreadContextMap map1 = createMap();
-        final DefaultThreadContextMap map2 = createMap();
-        assertEquals(map1.hashCode(), map2.hashCode());
-    }
-
-    @Test
-    public void testDoesNothingIfConstructedWithUseMapIsFalse() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(false);
-        assertTrue(map.isEmpty());
-        assertFalse(map.containsKey("key"));
-        map.put("key", "value");
-
-        assertTrue(map.isEmpty());
-        assertFalse(map.containsKey("key"));
-        assertNull(map.get("key"));
-    }
-
-    @Test
-    public void testPut() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        assertFalse(map.containsKey("key"));
-        map.put("key", "value");
-
-        assertFalse(map.isEmpty());
-        assertTrue(map.containsKey("key"));
-        assertEquals("value", map.get("key"));
-    }
-
-    @Test
-    public void testPutAll() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        assertFalse(map.containsKey("key"));
-        final int mapSize = 10;
-        final Map<String, String> newMap = new HashMap<>(mapSize);
-        for (int i = 1; i <= mapSize; i++) {
-            newMap.put("key" + i, "value" + i);
-        }
-        map.putAll(newMap);
-        assertFalse(map.isEmpty());
-        for (int i = 1; i <= mapSize; i++) {
-            assertTrue(map.containsKey("key" + i));
-            assertEquals("value" + i, map.get("key" + i));
-        }
-    }
-
-    /**
-     * Test method for
-     * {@link org.apache.logging.log4j.spi.DefaultThreadContextMap#remove(java.lang.String)}
-     * .
-     */
-    @Test
-    public void testRemove() {
-        final DefaultThreadContextMap map = createMap();
-        assertEquals("value", map.get("key"));
-        assertEquals("value2", map.get("key2"));
-
-        map.remove("key");
-        assertFalse(map.containsKey("key"));
-        assertEquals("value2", map.get("key2"));
-    }
-
-    @Test
-    public void testClear() {
-        final DefaultThreadContextMap map = createMap();
-
-        map.clear();
-        assertTrue(map.isEmpty());
-        assertFalse(map.containsKey("key"));
-        assertFalse(map.containsKey("key2"));
-    }
-
-    /**
-     * @return
-     */
-    private DefaultThreadContextMap createMap() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        map.put("key", "value");
-        map.put("key2", "value2");
-        assertEquals("value", map.get("key"));
-        assertEquals("value2", map.get("key2"));
-        return map;
-    }
-
-    @Test
-    public void testGetCopyReturnsMutableMap() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        final Map<String, String> copy = map.getCopy();
-        assertTrue(copy.isEmpty());
-
-        copy.put("key", "value"); // mutable
-        assertEquals("value", copy.get("key"));
-
-        // thread context map not affected
-        assertTrue(map.isEmpty());
-    }
-
-    @Test
-    public void testGetCopyReturnsMutableCopy() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        map.put("key1", "value1");
-        assertFalse(map.isEmpty());
-        final Map<String, String> copy = map.getCopy();
-        assertEquals("value1", copy.get("key1")); // copy has values too
-
-        copy.put("key", "value"); // copy is mutable
-        assertEquals("value", copy.get("key"));
-
-        // thread context map not affected
-        assertFalse(map.containsKey("key"));
-
-        // clearing context map does not affect copy
-        map.clear();
-        assertTrue(map.isEmpty());
-
-        assertFalse(copy.isEmpty());
-    }
-
-    @Test
-    public void testGetImmutableMapReturnsNullIfEmpty() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertTrue(map.isEmpty());
-        assertNull(map.getImmutableMapOrNull());
-    }
-
-    @Test
-    public void testGetImmutableMapReturnsImmutableMapIfNonEmpty() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        map.put("key1", "value1");
-        assertFalse(map.isEmpty());
-
-        final Map<String, String> immutable = map.getImmutableMapOrNull();
-        assertEquals("value1", immutable.get("key1")); // copy has values too
-
-        // immutable
-        assertThrows(UnsupportedOperationException.class, () -> immutable.put("key", "value"));
-    }
-
-    @Test
-    public void testGetImmutableMapCopyNotAffectedByContextMapChanges() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        map.put("key1", "value1");
-        assertFalse(map.isEmpty());
-
-        final Map<String, String> immutable = map.getImmutableMapOrNull();
-        assertEquals("value1", immutable.get("key1")); // copy has values too
-
-        // clearing context map does not affect copy
-        map.clear();
-        assertTrue(map.isEmpty());
-
-        assertFalse(immutable.isEmpty());
-    }
-
-    @Test
-    public void testToStringShowsMapContext() {
-        final DefaultThreadContextMap map = new DefaultThreadContextMap(true);
-        assertEquals("{}", map.toString());
-
-        map.put("key1", "value1");
-        assertEquals("{key1=value1}", map.toString());
-
-        map.remove("key1");
-        map.put("key2", "value2");
-        assertEquals("{key2=value2}", map.toString());
-    }
-
-    @Test
-    public void testThreadLocalNotInheritableByDefault() {
-        final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true, false);
-        assertFalse(threadLocal instanceof InheritableThreadLocal<?>);
-    }
-
-    @Test
-    public void testThreadLocalInheritableIfConfigured() {
-        final ThreadLocal<Map<String, String>> threadLocal = DefaultThreadContextMap.createThreadLocalMap(true, true);
-        assertTrue(threadLocal instanceof InheritableThreadLocal<?>);
-    }
-}
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java
index 4067a0bced..399e09a57d 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java
@@ -63,7 +63,6 @@ public class LegacyPropertiesCompatibilityTest {
             {"log4j2.asyncQueueFullPolicy", "AsyncLogger.queueFullPolicy"},
             {"log4j2.discardThreshold", "AsyncLogger.discardThreshold"},
             {"log4j2.isWebapp", "Web.isWebApp"},
-            {"log4j2.enableThreadlocals", "ThreadLocals.enable"},
             {"log4j2.enableDirectEncoders", "GC.enableDirectEncoders"},
             {"log4j2.initialReusableMsgSize", "GC.initialReusableMsgSize"},
             {"log4j2.maxReusableMsgSize", "GC.maxReusableMsgSize"},
diff --git a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java
index 02f5ec427b..b5ee10aa63 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/Unbox2ConfigurableTest.java
@@ -47,11 +47,6 @@ public class Unbox2ConfigurableTest {
         FieldUtils.writeDeclaredField(field, "modifiers", field.getModifiers() & ~Modifier.FINAL);
 
         field.set(null, 32); // reset to default
-
-        final Field threadLocalField = FieldUtils.getDeclaredField(Unbox.class, "threadLocalState", true);
-        final ThreadLocal<?> threadLocal = (ThreadLocal<?>) threadLocalField.get(null);
-        threadLocal.remove();
-        threadLocalField.set(null, new ThreadLocal<>());
     }
 
     @Test
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
index 4ac8f0706a..e5a3056b6d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/ThreadContext.java
@@ -24,7 +24,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.LoggingSystem;
 import org.apache.logging.log4j.spi.LoggingSystemProperty;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
@@ -311,15 +310,8 @@ public final class ThreadContext {
      * Returns a read-only view of the internal data structure used to store thread context key-value pairs,
      * or {@code null} if the internal data structure does not implement the
      * {@code ReadOnlyThreadContextMap} interface.
-     * <p>
-     * The {@link DefaultThreadContextMap} implementation does not implement {@code ReadOnlyThreadContextMap}, so by
-     * default this method returns {@code null}.
-     * </p>
      *
      * @return the internal data structure used to store thread context key-value pairs or {@code null}
-     * @see DefaultThreadContextMap
-     * @see org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap
-     * @see org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap
      * @since 2.8
      */
     public static ReadOnlyThreadContextMap getThreadContextMap() {
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
index 6cdbba0863..ebc9b7895e 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/recycler/ThreadLocalRecyclerFactoryProvider.java
@@ -55,11 +55,10 @@ public final class ThreadLocalRecyclerFactoryProvider implements RecyclerFactory
 
     @Nullable
     @Override
-    public RecyclerFactory createForEnvironment(PropertyEnvironment environment) {
+    public RecyclerFactory createForEnvironment(final PropertyEnvironment environment) {
         requireNonNull(environment, "environment");
-        final boolean threadLocalEnabled =
-                environment.getBooleanProperty(LoggingSystemProperty.THREAD_LOCALS_ENABLE, true);
-        if (!threadLocalEnabled) {
+        final boolean webApp = environment.getBooleanProperty(LoggingSystemProperty.IS_WEBAPP, false);
+        if (webApp) {
             return null;
         }
         final int capacity = environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, DEFAULT_CAPACITY);
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
index 21176cd0c5..b63f61a5ce 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
@@ -23,7 +23,6 @@ import org.apache.logging.log4j.spi.recycler.Recycler;
  * If messages created by an implementation are reusable, then the {@link #recycle(Message)} method must
  * be implemented and consistently used for returning objects to be reused.
  *
- * @see ParameterizedMessageFactory
  * @see StringFormatterMessageFactory
  */
 public interface MessageFactory {
@@ -58,8 +57,6 @@ public interface MessageFactory {
      * @param params
      *            the message parameters
      * @return a new message
-     * @see ParameterizedMessageFactory
-     * @see StringFormatterMessageFactory
      */
     Message newMessage(String message, Object... params);
 
@@ -78,7 +75,6 @@ public interface MessageFactory {
      * @param message a message template, the kind of message template depends on the implementation.
      * @param p0 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(final String message, final Object p0) {
         return newMessage(message, new Object[] {p0});
@@ -91,7 +87,6 @@ public interface MessageFactory {
      * @param p0 a message parameter
      * @param p1 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(final String message, final Object p0, final Object p1) {
         return newMessage(message, new Object[] {p0, p1});
@@ -105,7 +100,6 @@ public interface MessageFactory {
      * @param p1 a message parameter
      * @param p2 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
         return newMessage(message, new Object[] {p0, p1, p2});
@@ -120,7 +114,6 @@ public interface MessageFactory {
      * @param p2 a message parameter
      * @param p3 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message, final Object p0, final Object p1, final Object p2, final Object p3) {
@@ -137,7 +130,6 @@ public interface MessageFactory {
      * @param p3 a message parameter
      * @param p4 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
@@ -155,7 +147,6 @@ public interface MessageFactory {
      * @param p4 a message parameter
      * @param p5 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message,
@@ -180,7 +171,6 @@ public interface MessageFactory {
      * @param p5 a message parameter
      * @param p6 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message,
@@ -207,7 +197,6 @@ public interface MessageFactory {
      * @param p6 a message parameter
      * @param p7 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message,
@@ -236,7 +225,6 @@ public interface MessageFactory {
      * @param p7 a message parameter
      * @param p8 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message,
@@ -267,7 +255,6 @@ public interface MessageFactory {
      * @param p8 a message parameter
      * @param p9 a message parameter
      * @return a new message
-     * @see ParameterizedMessageFactory
      */
     default Message newMessage(
             final String message,
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java
index 612b38f996..23b280ab12 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java
@@ -19,7 +19,6 @@ package org.apache.logging.log4j.message;
 /**
  * Creates messages. Implementations can provide different message format syntaxes.
  *
- * @see ParameterizedMessageFactory
  * @deprecated MessageFactory now contains all interface methods.
  * @since 2.6
  */
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java
deleted file mode 100644
index 344e8fe01d..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.message;
-
-/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory}.
- * <p>
- * Enables the use of <code>{}</code> parameter markers in message strings.
- * </p>
- * <p>
- * Creates {@link ParameterizedMessage} instances for {@link #newMessage(String, Object...)}.
- * </p>
- * <p>
- * This class is immutable.
- * </p>
- *
- * <p>
- * <strong>Note to implementors:</strong>
- * </p>
- * <p>
- * This class implements all {@link MessageFactory} methods.
- * </p>
- */
-public final class ParameterizedMessageFactory implements MessageFactory {
-    /**
-     * Instance of ParameterizedMessageFactory.
-     */
-    public static final ParameterizedMessageFactory INSTANCE = new ParameterizedMessageFactory();
-
-    /**
-     * Constructs a message factory.
-     */
-    public ParameterizedMessageFactory() {
-        super();
-    }
-
-    /**
-     * Creates {@link ParameterizedMessage} instances.
-     *
-     * @param message The message pattern.
-     * @param params The message parameters.
-     * @return The Message.
-     *
-     * @see MessageFactory#newMessage(String, Object...)
-     */
-    @Override
-    public Message newMessage(final String message, final Object... params) {
-        return new ParameterizedMessage(message, params);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(final String message, final Object p0) {
-        return new ParameterizedMessage(message, p0);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(final String message, final Object p0, final Object p1) {
-        return new ParameterizedMessage(message, p0, p1);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
-        return new ParameterizedMessage(message, p0, p1, p2);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message, final Object p0, final Object p1, final Object p2, final Object p3) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3, p4);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message,
-            final Object p0,
-            final Object p1,
-            final Object p2,
-            final Object p3,
-            final Object p4,
-            final Object p5) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message,
-            final Object p0,
-            final Object p1,
-            final Object p2,
-            final Object p3,
-            final Object p4,
-            final Object p5,
-            final Object p6) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message,
-            final Object p0,
-            final Object p1,
-            final Object p2,
-            final Object p3,
-            final Object p4,
-            final Object p5,
-            final Object p6,
-            final Object p7) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message,
-            final Object p0,
-            final Object p1,
-            final Object p2,
-            final Object p3,
-            final Object p4,
-            final Object p5,
-            final Object p6,
-            final Object p7,
-            final Object p8) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
-    }
-
-    /**
-     * @since 2.6.1
-     */
-    @Override
-    public Message newMessage(
-            final String message,
-            final Object p0,
-            final Object p1,
-            final Object p2,
-            final Object p3,
-            final Object p4,
-            final Object p5,
-            final Object p6,
-            final Object p7,
-            final Object p8,
-            final Object p9) {
-        return new ParameterizedMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
index 7abd8ac064..3b71f2896f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
@@ -23,9 +23,8 @@ import org.apache.logging.log4j.util.PerformanceSensitive;
 
 /**
  * Implementation of the {@link MessageFactory} interface that avoids allocating temporary objects where possible.
- * Message instances are cached in a ThreadLocal and reused when a new message is requested within the same thread.
+ * Message instances are cached in a {@link Recycler} and reused when a new message is requested.
  * Messages returned from this factory must be {@linkplain #recycle(Message) recycled} when done using.
- * @see ParameterizedMessageFactory
  * @see ReusableSimpleMessage
  * @see ReusableObjectMessage
  * @see ReusableParameterizedMessage
@@ -33,15 +32,10 @@ import org.apache.logging.log4j.util.PerformanceSensitive;
  * @since 2.6
  */
 @PerformanceSensitive("allocation")
-/*
- * https://errorprone.info/bugpattern/ThreadLocalUsage
- * Instance thread locals are not a problem here, since this class is almost a singleton.
- */
-@SuppressWarnings("ThreadLocalUsage")
 public final class ReusableMessageFactory implements MessageFactory {
 
     /**
-     * Instance of ReusableMessageFactory..
+     * Instance of {@link ReusableMessageFactory}.
      */
     public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory();
 
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
deleted file mode 100644
index c9ec6b1f2b..0000000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextMap.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.spi;
-
-import static org.apache.logging.log4j.spi.LoggingSystemProperty.THREAD_CONTEXT_MAP_INHERITABLE;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import org.apache.logging.log4j.util.BiConsumer;
-import org.apache.logging.log4j.util.Cast;
-import org.apache.logging.log4j.util.PropertiesUtil;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
-import org.apache.logging.log4j.util.TriConsumer;
-
-/**
- * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always
- * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is
- * expected that the Map will be passed to many more log events than the number of keys it contains the performance
- * should be much better than if the Map was copied for each event.
- */
-public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap {
-
-    private final boolean useMap;
-    private final ThreadLocal<Map<String, String>> localMap;
-
-    static ThreadLocal<Map<String, String>> createThreadLocalMap(
-            final boolean isMapEnabled, final boolean inheritableMap) {
-        if (inheritableMap) {
-            return new InheritableThreadLocal<>() {
-                @Override
-                protected Map<String, String> childValue(final Map<String, String> parentValue) {
-                    return parentValue != null && isMapEnabled ? Map.copyOf(parentValue) : null;
-                }
-            };
-        }
-        // if not inheritable, return plain ThreadLocal with null as initial value
-        return new ThreadLocal<>();
-    }
-
-    public DefaultThreadContextMap() {
-        this(true);
-    }
-
-    public DefaultThreadContextMap(final boolean useMap) {
-        this(useMap, PropertiesUtil.getProperties().getBooleanProperty(THREAD_CONTEXT_MAP_INHERITABLE));
-    }
-
-    DefaultThreadContextMap(final boolean useMap, final boolean inheritableMap) {
-        this.useMap = useMap;
-        this.localMap = createThreadLocalMap(useMap, inheritableMap);
-    }
-
-    @Override
-    public void put(final String key, final String value) {
-        if (!useMap) {
-            return;
-        }
-        Map<String, String> map = localMap.get();
-        map = map == null ? new HashMap<>(1) : new HashMap<>(map);
-        map.put(key, value);
-        localMap.set(Collections.unmodifiableMap(map));
-    }
-
-    @Override
-    public void putAll(final Map<String, String> m) {
-        if (!useMap) {
-            return;
-        }
-        Map<String, String> map = localMap.get();
-        map = map == null ? new HashMap<>(m.size()) : new HashMap<>(map);
-        map.putAll(m);
-        localMap.set(Collections.unmodifiableMap(map));
-    }
-
-    @Override
-    public String get(final String key) {
-        final Map<String, String> map = localMap.get();
-        return map == null ? null : map.get(key);
-    }
-
-    @Override
-    public void remove(final String key) {
-        final Map<String, String> map = localMap.get();
-        if (map != null) {
-            final Map<String, String> copy = new HashMap<>(map);
-            copy.remove(key);
-            localMap.set(Collections.unmodifiableMap(copy));
-        }
-    }
-
-    @Override
-    public void removeAll(final Iterable<String> keys) {
-        final Map<String, String> map = localMap.get();
-        if (map != null) {
-            final Map<String, String> copy = new HashMap<>(map);
-            for (final String key : keys) {
-                copy.remove(key);
-            }
-            localMap.set(Collections.unmodifiableMap(copy));
-        }
-    }
-
-    @Override
-    public void clear() {
-        localMap.remove();
-    }
-
-    @Override
-    public Map<String, String> toMap() {
-        return getCopy();
-    }
-
-    @Override
-    public boolean containsKey(final String key) {
-        final Map<String, String> map = localMap.get();
-        return map != null && map.containsKey(key);
-    }
-
-    @Override
-    public <V> void forEach(final BiConsumer<String, ? super V> action) {
-        final Map<String, String> map = localMap.get();
-        if (map == null) {
-            return;
-        }
-        for (final Map.Entry<String, String> entry : map.entrySet()) {
-            // BiConsumer should be able to handle values of any type V. In our case the values are of type String.
-            final V value = Cast.cast(entry.getValue());
-            action.accept(entry.getKey(), value);
-        }
-    }
-
-    @Override
-    public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
-        final Map<String, String> map = localMap.get();
-        if (map == null) {
-            return;
-        }
-        for (final Map.Entry<String, String> entry : map.entrySet()) {
-            // TriConsumer should be able to handle values of any type V. In our case the values are of type String.
-            final V value = Cast.cast(entry.getValue());
-            action.accept(entry.getKey(), value, state);
-        }
-    }
-
-    @Override
-    public <V> V getValue(final String key) {
-        final Map<String, String> map = localMap.get();
-        return map == null ? null : Cast.cast(map.get(key));
-    }
-
-    @Override
-    public Map<String, String> getCopy() {
-        final Map<String, String> map = localMap.get();
-        return map == null ? new HashMap<>() : new HashMap<>(map);
-    }
-
-    @Override
-    public Map<String, String> getImmutableMapOrNull() {
-        return localMap.get();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        final Map<String, String> map = localMap.get();
-        return map == null || map.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        final Map<String, String> map = localMap.get();
-        return map == null ? 0 : map.size();
-    }
-
-    @Override
-    public String toString() {
-        final Map<String, String> map = localMap.get();
-        return map == null ? "{}" : map.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        final Map<String, String> map = this.localMap.get();
-        result = prime * result + ((map == null) ? 0 : map.hashCode());
-        result = prime * result + Boolean.valueOf(this.useMap).hashCode();
-        return result;
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (obj instanceof DefaultThreadContextMap) {
-            final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
-            if (this.useMap != other.useMap) {
-                return false;
-            }
-        }
-        if (!(obj instanceof ThreadContextMap)) {
-            return false;
-        }
-        final ThreadContextMap other = (ThreadContextMap) obj;
-        final Map<String, String> map = this.localMap.get();
-        final Map<String, String> otherMap = other.getImmutableMapOrNull();
-        return Objects.equals(map, otherMap);
-    }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
index 46cf2ae91f..02ad1242e7 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystem.java
@@ -44,12 +44,10 @@ import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.ParameterizedMessageFactory;
 import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
 import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
-import org.apache.logging.log4j.util.Constants;
 import org.apache.logging.log4j.util.Lazy;
 import org.apache.logging.log4j.util.LoaderUtil;
 import org.apache.logging.log4j.util.LowLevelLogUtil;
@@ -90,7 +88,7 @@ public class LoggingSystem {
                 return factory;
             }
         }
-        return Constants.isThreadLocalsEnabled() ? new ReusableMessageFactory() : new ParameterizedMessageFactory();
+        return new ReusableMessageFactory();
     });
     private final Lazy<FlowMessageFactory> flowMessageFactoryLazy = environmentLazy.map(environment -> {
         final String className = environment.getStringProperty(LOGGER_FLOW_MESSAGE_FACTORY_CLASS);
@@ -312,7 +310,7 @@ public class LoggingSystem {
         /**
          * Creates the ThreadContextMap instance used by the ThreadContext.
          * <p>
-         * If {@linkplain Constants#isThreadLocalsEnabled() Log4j can use ThreadLocals}, a garbage-free StringMap-based context map can
+         * A garbage-free StringMap-based context map can
          * be installed by setting system property {@link LoggingSystemProperty#THREAD_CONTEXT_GARBAGE_FREE_ENABLED} to {@code true}.
          * </p><p>
          * Furthermore, any custom {@code ThreadContextMap} can be installed by setting system property
@@ -351,18 +349,14 @@ public class LoggingSystem {
                     return map;
                 }
             }
-            final boolean threadLocalsEnabled = Constants.isThreadLocalsEnabled();
             final boolean garbageFreeEnabled = environment.getBooleanProperty(THREAD_CONTEXT_GARBAGE_FREE_ENABLED);
             final boolean inheritableMap = environment.getBooleanProperty(THREAD_CONTEXT_MAP_INHERITABLE);
             final int initialCapacity = environment.getIntegerProperty(
                     THREAD_CONTEXT_INITIAL_CAPACITY, THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY);
-            if (threadLocalsEnabled) {
-                if (garbageFreeEnabled) {
-                    return new GarbageFreeSortedArrayThreadContextMap(inheritableMap, initialCapacity);
-                }
-                return new CopyOnWriteSortedArrayThreadContextMap(inheritableMap, initialCapacity);
+            if (garbageFreeEnabled) {
+                return new GarbageFreeSortedArrayThreadContextMap(inheritableMap, initialCapacity);
             }
-            return new DefaultThreadContextMap(true, inheritableMap);
+            return new CopyOnWriteSortedArrayThreadContextMap(inheritableMap, initialCapacity);
         }
 
         public ThreadContextStack createContextStack(final PropertyEnvironment environment) {
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
index 90ae13311d..fa839f076c 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggingSystemProperty.java
@@ -45,13 +45,6 @@ public enum LoggingSystemProperty implements PropertyKey {
      */
     // GC.maxReusableMsgSize
     GC_REUSABLE_MESSAGE_MAX_SIZE(PropertyComponent.GC, Constant.MAX_REUSABLE_MSG_SIZE),
-    /**
-     * Property to override the use of thread-local values for garbage-free logging.
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1270">LOG4J2-1270</a>
-     */
-    // ThreadLocals.enabled : calculate | true | false
-    THREAD_LOCALS_ENABLE(PropertyComponent.THREAD_LOCALs, Constant.ENABLE),
 
     /**
      * Property to ignore the thread context ClassLoader when set to {@code true}.
@@ -280,12 +273,9 @@ public enum LoggingSystemProperty implements PropertyKey {
          *
          * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1270">LOG4J2-1270</a>
          */
-        // ThreadLocals.enabled : calculate | true | false
+        // ThreadContext.enabled : calculate | true | false
         static final String ENABLE = "enable";
 
-        public static final String THREAD_LOCALS_ENABLE =
-                SYSTEM_PROPERTY_PREFIX + PropertyComponent.Constant.THREAD_LOCALS + DELIM + ENABLE;
-
         /**
          * Property to ignore the thread context ClassLoader when set to {@code true}.
          *
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
index 5dd99ef77a..b359b60c64 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/PropertyComponent.java
@@ -41,7 +41,6 @@ public enum PropertyComponent {
     STATUS_LOGGER(Constant.STATUS_LOGGER),
     SYSTEM(Constant.SYSTEM),
     THREAD_CONTEXT(Constant.THREAD_CONTEXT),
-    THREAD_LOCALs(Constant.THREAD_LOCALS),
     TRANSPORT_SECURITY(Constant.TRANSPORT_SECURITY),
     UUID(Constant.UUID),
     WEB(Constant.WEB);
@@ -81,7 +80,6 @@ public enum PropertyComponent {
         public static final String STATUS_LOGGER = "StatusLogger";
         public static final String SYSTEM = "System";
         public static final String THREAD_CONTEXT = "ThreadContext";
-        public static final String THREAD_LOCALS = "ThreadLocals";
         public static final String TRANSPORT_SECURITY = "TransportSecurity";
         public static final String UUID = "UUID";
         public static final String WEB = "Web";
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java
index c2e47aba35..78fd2eccc1 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Constants.java
@@ -49,28 +49,6 @@ public final class Constants {
         isWebApp.reset();
     }
 
-    private static final LazyBoolean threadLocalsEnabled = new LazyBoolean(() -> !isWebApp()
-            && PropertiesUtil.getProperties().getBooleanProperty(LoggingSystemProperty.THREAD_LOCALS_ENABLE, true));
-
-    /**
-     * Kill switch for object pooling in ThreadLocals that enables much of the LOG4J2-1270 no-GC behaviour.
-     * <p>
-     * {@code True} for non-{@link #isWebApp()} web apps}, disable by setting system property
-     * "log4j2.enable.threadlocals" to "false".
-     * </p>
-     */
-    public static boolean isThreadLocalsEnabled() {
-        return threadLocalsEnabled.getAsBoolean();
-    }
-
-    public static void setThreadLocalsEnabled(final boolean enabled) {
-        threadLocalsEnabled.setAsBoolean(enabled);
-    }
-
-    public static void resetThreadLocalsEnabled() {
-        threadLocalsEnabled.reset();
-    }
-
     public static final int JAVA_MAJOR_VERSION = getMajorVersion();
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java
index 9984f53d4d..e08b3609db 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java
@@ -90,24 +90,6 @@ public class Unbox {
         }
     }
 
-    private static class State {
-        private final StringBuilder[] ringBuffer = new StringBuilder[RINGBUFFER_SIZE];
-        private int current;
-
-        State() {
-            for (int i = 0; i < ringBuffer.length; i++) {
-                ringBuffer[i] = new StringBuilder(21);
-            }
-        }
-
-        public StringBuilder getStringBuilder() {
-            final StringBuilder result = ringBuffer[MASK & current++];
-            result.setLength(0);
-            return result;
-        }
-    }
-
-    private static final ThreadLocal<State> threadLocalState = new ThreadLocal<>();
     private static final WebSafeState webSafeState = new WebSafeState();
 
     private Unbox() {
@@ -243,17 +225,8 @@ public class Unbox {
         return getSB().append(value);
     }
 
-    private static State getState() {
-        State state = threadLocalState.get();
-        if (state == null) {
-            state = new State();
-            threadLocalState.set(state);
-        }
-        return state;
-    }
-
     private static StringBuilder getSB() {
-        return Constants.isThreadLocalsEnabled() ? getState().getStringBuilder() : webSafeState.getStringBuilder();
+        return webSafeState.getStringBuilder();
     }
 
     /** For testing. */
diff --git a/log4j-api/src/main/resources/log4j2.propertyMapping.json b/log4j-api/src/main/resources/log4j2.propertyMapping.json
index 6a07494108..5edb3f7bf9 100644
--- a/log4j-api/src/main/resources/log4j2.propertyMapping.json
+++ b/log4j-api/src/main/resources/log4j2.propertyMapping.json
@@ -4,7 +4,6 @@
     "debug": ["StatusLogger", "enableDebug"],
     "defaultStatusLevel": ["StatusLogger", "defaultLevel"],
     "garbagefreeThreadContextMap": ["ThreadContext", "garbageFreeMap"],
-    "enableThreadlocals": ["ThreadLocals", "enable"],
     "forceTCLOnly": ["Loader", "forceTCLOnly"],
     "ignoreTCL": ["Loader", "ignoreTCL"],
     "initialReusableMsgSize": ["GC", "initialReusableMsgSize"],
@@ -36,7 +35,6 @@
     "DEBUG": ["StatusLogger", "enableDebug"],
     "DEFAULT_STATUS_LEVEL": ["StatusLogger", "defaultLevel"],
     "GARBAGEFREE_THREAD_CONTEXT_MAP": ["ThreadContext", "garbageFreeMap"],
-    "ENABLE_THREADLOCALS": ["ThreadLocals", "enable"],
     "FORCE_TCL_ONLY": ["Loader", "forceTCLOnly"],
     "IGNORE_TCL": ["Loader", "ignoreTCL"],
     "INITIAL_REUSABLE_MSG_SIZE": ["GC", "initialReusableMsgSize"],
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java
index b67d39e2a5..f1c7d5e7d9 100644
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/ResponseTimeTest.java
@@ -28,7 +28,6 @@ import org.HdrHistogram.Histogram;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.async.DefaultAsyncQueueFullPolicy;
 import org.apache.logging.log4j.core.async.EventRoute;
-import org.apache.logging.log4j.core.util.Constants;
 import org.apache.logging.log4j.core.util.Integers;
 import org.apache.logging.log4j.core.util.Loader;
 
@@ -160,10 +159,6 @@ public class ResponseTimeTest {
                 warmupResponseTmHistograms,
                 threadCount);
         System.out.println("-----------------Warmup done. load=" + loadMessagesPerSec);
-        if (!Constants.ENABLE_DIRECT_ENCODERS || !Constants.ENABLE_THREADLOCALS) {
-            // System.gc();
-            // Thread.sleep(5000);
-        }
         System.out.println("-----------------Starting measured run. load=" + loadMessagesPerSec);
 
         final long start = System.currentTimeMillis();
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java
index d1de5621d2..11ac9f5801 100644
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/async/perftest/SimplePerfTest.java
@@ -19,15 +19,12 @@ package org.apache.logging.log4j.core.async.perftest;
 import java.lang.management.GarbageCollectorMXBean;
 import java.lang.management.ManagementFactory;
 import java.lang.management.RuntimeMXBean;
-import java.lang.reflect.Method;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.async.AsyncLogger;
-import org.apache.logging.log4j.core.async.AsyncLoggerContext;
 import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
-import org.apache.logging.log4j.spi.LoggerContext;
 
 /**
  * Created by remko on 2/26/2016.
@@ -44,8 +41,6 @@ public class SimplePerfTest {
         if (!(logger instanceof AsyncLogger)) {
             throw new IllegalStateException();
         }
-        // work around a bug in Log4j-2.5
-        workAroundLog4j2_5Bug();
 
         logger.error("Starting...");
         System.out.println("Starting...");
@@ -143,15 +138,4 @@ public class SimplePerfTest {
             logger.error("simple text message");
         }
     }
-
-    private static void workAroundLog4j2_5Bug() {
-        // use reflection so we can use the same test with older versions of log4j2
-        try {
-            final Method setUseThreadLocals =
-                    AsyncLoggerContext.class.getDeclaredMethod("setUseThreadLocals", boolean.class);
-            final LoggerContext context = LogManager.getContext(false);
-            setUseThreadLocals.invoke(context, Boolean.TRUE);
-        } catch (final Throwable ignored) {
-        }
-    }
 }
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
index f7ed642eaa..4226c55734 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
@@ -41,7 +41,7 @@ import org.apache.logging.log4j.core.test.junit.Named;
 import org.apache.logging.log4j.core.test.junit.ReconfigurationPolicy;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.StringFormatterMessageFactory;
 import org.apache.logging.log4j.message.StructuredDataMessage;
@@ -383,7 +383,7 @@ public class LoggerTest {
         final Logger testLogger = testMessageFactoryMismatch(
                 testInfo.getTestMethod().map(Method::getName).orElseThrow(AssertionError::new),
                 StringFormatterMessageFactory.INSTANCE,
-                ParameterizedMessageFactory.INSTANCE);
+                new ReusableMessageFactory());
         testLogger.debug("%,d", Integer.MAX_VALUE);
         final List<LogEvent> events = app.getEvents();
         assertEventCount(events, 1);
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java
index b63486caec..3a776f9417 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessorTest.java
@@ -25,6 +25,7 @@ import java.time.ZoneId;
 import java.util.Calendar;
 import java.util.Locale;
 import java.util.TimeZone;
+import org.apache.logging.log4j.core.config.NullConfiguration;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.parallel.ResourceAccessMode;
 import org.junit.jupiter.api.parallel.ResourceLock;
@@ -42,7 +43,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testDontInterpretBackslashAsEscape() {
-        final PatternProcessor pp = new PatternProcessor("c:\\test\\new/app-%d{HH-mm-ss}.log");
+        final PatternProcessor pp = createPatternProcessor("c:\\test\\new/app-%d{HH-mm-ss}.log");
         final Calendar cal = Calendar.getInstance();
         cal.set(Calendar.HOUR_OF_DAY, 16);
         cal.set(Calendar.MINUTE, 2);
@@ -56,7 +57,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz");
         final Instant initial = parseLocalDateTime("2014-03-04T10:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -67,7 +68,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeHourlyReturnsFirstMinuteOfNextHour2() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz");
         final Instant initial = parseLocalDateTime("2014-03-04T23:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -81,7 +82,7 @@ public class PatternProcessorTest {
         // America/Chicago 2014 - DST start - Mar 9 02:00
         // during winter GMT-6
         // during summer GMT-5
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-03-09T01:31:59-06:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -97,7 +98,7 @@ public class PatternProcessorTest {
         // America/Chicago 2014 - DST end - Nov 2 02:00
         // during summer GMT-5
         // during winter GMT-6
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH}{America/Chicago}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-11-02T01:31:59-05:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -111,7 +112,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeHourlyReturnsFirstMinuteOfNextYear() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH}.log.gz");
         final Instant initial = parseLocalDateTime("2015-12-31T23:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -122,7 +123,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeMillisecondlyReturnsNextMillisec() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss.SSS}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss.SSS}.log.gz");
         final Instant initial = parseLocalDateTime("2014-03-04T10:31:53.123");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -133,7 +134,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeMinutelyReturnsFirstSecondOfNextMinute() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm}.log.gz");
         final Instant initial = parseLocalDateTime("2014-03-04T10:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -144,7 +145,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
         final Instant initial = parseLocalDateTime("2014-10-15T10:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -155,7 +156,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth2() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
         final Instant initial = parseLocalDateTime("2014-01-31T10:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -167,7 +168,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeMonthlyReturnsFirstDayOfNextMonth3() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
         final Instant initial = parseLocalDateTime("2014-12-31T10:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -179,7 +180,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeMonthlyReturnsFirstDayOfNextYear() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM}.log.gz");
         final Instant initial = parseLocalDateTime("2015-12-28T00:00:00");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -191,7 +192,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeSecondlyReturnsFirstMillisecOfNextSecond() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd-HH-mm-ss}.log.gz");
         final Instant initial = parseLocalDateTime("2014-03-04T10:31:53.123");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -206,7 +207,7 @@ public class PatternProcessorTest {
         Locale.setDefault(Locale.FRANCE); // force 1st day of the week to be Monday
 
         try {
-            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-W}.log.gz");
+            final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-W}.log.gz");
             final Instant initial = parseLocalDateTime("2014-03-04T10:31:59");
             final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -224,7 +225,7 @@ public class PatternProcessorTest {
         Locale.setDefault(Locale.US); // force 1st day of the week to be Sunday
 
         try {
-            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-W}.log.gz");
+            final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-W}.log.gz");
             final Instant initial = parseLocalDateTime("2014-03-04T10:31:59");
             final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -244,7 +245,7 @@ public class PatternProcessorTest {
         final Locale old = Locale.getDefault();
         Locale.setDefault(Locale.US); // force 1st day of the week to be Sunday
         try {
-            final PatternProcessor pp = new PatternProcessor("logs/market_data_msg.log-%d{yyyy-MM-'W'W}");
+            final PatternProcessor pp = createPatternProcessor("logs/market_data_msg.log-%d{yyyy-MM-'W'W}");
             final Instant initial = parseLocalDateTime("2015-12-28T00:00:00");
             final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -258,7 +259,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeDailyReturnsFirstHourOfNextDay() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}.log.gz");
         final Instant initial = parseLocalDateTime("2014-03-04T02:31:59");
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -269,7 +270,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeDailyReturnsFirstHourOfNextDayHonoringTimeZoneOption1() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-03-04T02:31:59-06:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -286,7 +287,7 @@ public class PatternProcessorTest {
         final TimeZone old = TimeZone.getDefault();
         TimeZone.setDefault(TimeZone.getTimeZone("GMT+10")); // default is ignored if pattern contains timezone
         try {
-            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
+            final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
             final Instant initial =
                     OffsetDateTime.parse("2014-03-04T02:31:59-06:00").toInstant();
             final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -306,7 +307,7 @@ public class PatternProcessorTest {
         final TimeZone old = TimeZone.getDefault();
         TimeZone.setDefault(TimeZone.getTimeZone("GMT-10")); // default is ignored if pattern contains timezone
         try {
-            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
+            final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{GMT-6}.log.gz");
             final Instant initial =
                     OffsetDateTime.parse("2014-03-04T02:31:59-06:00").toInstant();
             final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -322,7 +323,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeDailyReturnsFirstHourOfNextDayDstJan() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-01-04T00:31:59-06:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -335,7 +336,7 @@ public class PatternProcessorTest {
     @Test
     @ResourceLock(value = Resources.LOCALE, mode = ResourceAccessMode.READ)
     public void testGetNextTimeDailyReturnsFirstHourOfNextDayDstJun() {
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-06-04T00:31:59-05:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -351,7 +352,7 @@ public class PatternProcessorTest {
         // America/Chicago 2014 - DST start - Mar 9 02:00
         // during winter GMT-6
         // during summer GMT-5
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-03-09T00:31:59-06:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -367,7 +368,7 @@ public class PatternProcessorTest {
         // America/Chicago 2014 - DST end - Nov 2 02:00
         // during summer GMT-5
         // during winter GMT-6
-        final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
+        final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{America/Chicago}.log.gz");
         final Instant initial =
                 OffsetDateTime.parse("2014-11-02T00:31:59-05:00").toInstant();
         final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
@@ -384,7 +385,7 @@ public class PatternProcessorTest {
         final TimeZone old = TimeZone.getDefault();
         TimeZone.setDefault(TimeZone.getTimeZone("GMT-10")); // default is ignored even if timezone option invalid
         try {
-            final PatternProcessor pp = new PatternProcessor("logs/app-%d{yyyy-MM-dd}{NOTVALID}.log.gz");
+            final PatternProcessor pp = createPatternProcessor("logs/app-%d{yyyy-MM-dd}{NOTVALID}.log.gz");
             final Instant initial = Instant.parse("2014-03-04T02:31:59Z");
             final long actual = pp.getNextTime(initial.toEpochMilli(), 1, false);
 
@@ -394,4 +395,8 @@ public class PatternProcessorTest {
             TimeZone.setDefault(old);
         }
     }
+
+    private static PatternProcessor createPatternProcessor(final String pattern) {
+        return new PatternProcessor(new NullConfiguration(), pattern);
+    }
 }
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
index 35ec4b01bb..875f88173f 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
@@ -41,6 +41,7 @@ import java.nio.file.attribute.PosixFilePermissions;
 import java.util.Set;
 import java.util.concurrent.locks.LockSupport;
 import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.NullConfiguration;
 import org.apache.logging.log4j.core.util.Closer;
 import org.apache.logging.log4j.core.util.FileUtils;
 import org.apache.logging.log4j.util.Strings;
@@ -68,6 +69,7 @@ public class RollingRandomAccessFileManagerTest {
             final TriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(triggerSize);
             final RolloverStrategy rolloverStrategy = null;
             final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(
+                    new NullConfiguration(),
                     null,
                     raf,
                     file.getName(),
@@ -113,6 +115,7 @@ public class RollingRandomAccessFileManagerTest {
             final TriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(triggerSize);
             final RolloverStrategy rolloverStrategy = null;
             final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(
+                    new NullConfiguration(),
                     null,
                     raf,
                     file.getName(),
@@ -157,6 +160,7 @@ public class RollingRandomAccessFileManagerTest {
             assertNotEquals(bufferSize, RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE);
             final RolloverStrategy rolloverStrategy = null;
             final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(
+                    new NullConfiguration(),
                     null,
                     raf,
                     file.getName(),
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java
deleted file mode 100644
index 96d4d54adf..0000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.async;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertSame;
-
-import org.apache.logging.log4j.core.impl.Log4jPropertyKey;
-import org.junit.jupiter.api.Tag;
-import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.SetSystemProperty;
-
-@Tag("async")
-public class AsyncLoggerThreadNameStrategyTest {
-
-    @Test
-    public void testDefaultIfNotConfigured() throws Exception {
-        final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create();
-        assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns);
-    }
-
-    @Test
-    @SetSystemProperty(key = Log4jPropertyKey.Constant.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "\\%%InValid ")
-    public void testDefaultIfInvalidConfig() throws Exception {
-        final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create();
-        assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns);
-    }
-
-    @Test
-    @SetSystemProperty(key = Log4jPropertyKey.Constant.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "CACHED")
-    public void testUseCachedThreadNameIfConfigured() throws Exception {
-        final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create();
-        assertSame(ThreadNameCachingStrategy.CACHED, tns);
-    }
-
-    @Test
-    @SetSystemProperty(key = Log4jPropertyKey.Constant.ASYNC_LOGGER_THREAD_NAME_STRATEGY, value = "UNCACHED")
-    public void testUseUncachedThreadNameIfConfigured() throws Exception {
-        final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create();
-        assertSame(ThreadNameCachingStrategy.UNCACHED, tns);
-    }
-
-    @Test
-    public void testUncachedThreadNameStrategyReturnsCurrentThreadName() throws Exception {
-        final String name1 = "MODIFIED-THREADNAME1";
-        Thread.currentThread().setName(name1);
-        assertEquals(name1, ThreadNameCachingStrategy.UNCACHED.getThreadName());
-
-        final String name2 = "OTHER-THREADNAME2";
-        Thread.currentThread().setName(name2);
-        assertEquals(name2, ThreadNameCachingStrategy.UNCACHED.getThreadName());
-    }
-
-    @Test
-    public void testCachedThreadNameStrategyReturnsCachedThreadName() throws Exception {
-        final String original = "Original-ThreadName";
-        Thread.currentThread().setName(original);
-        assertEquals(original, ThreadNameCachingStrategy.CACHED.getThreadName());
-
-        final String name2 = "OTHER-THREADNAME2";
-        Thread.currentThread().setName(name2);
-        assertEquals(original, ThreadNameCachingStrategy.CACHED.getThreadName());
-    }
-}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java
index 8a86a4628f..af93e2615e 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/AsyncThreadContextTest.java
@@ -37,7 +37,6 @@ import org.apache.logging.log4j.core.selector.ContextSelector;
 import org.apache.logging.log4j.core.test.CoreLoggerContexts;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.plugins.di.DI;
-import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.LoggingSystemProperty;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
 import org.apache.logging.log4j.test.TestProperties;
@@ -98,7 +97,6 @@ public class AsyncThreadContextTest {
         public String implClassSimpleName() {
             switch (this) {
                 case WEBAPP:
-                    return DefaultThreadContextMap.class.getSimpleName();
                 case GARBAGE_FREE:
                     return "GarbageFreeSortedArrayThreadContextMap";
                 case COPY_ON_WRITE:
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
index 6b0d2e2aac..e0b722a913 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
@@ -28,7 +28,6 @@ import org.junit.jupiter.api.Test;
 /**
  * Tests queue full scenarios with AsyncLoggers in configuration.
  */
-@SetTestProperty(key = "ThreadLocals.enable", value = "true")
 @SetTestProperty(key = "Web.isWebApp", value = "false")
 @SetTestProperty(key = "AsyncLoggerConfig.ringBufferSize", value = "128")
 public class QueueFullAsyncLoggerConfigLoggingFromToStringTest extends QueueFullAbstractTest {
@@ -45,6 +44,5 @@ public class QueueFullAsyncLoggerConfigLoggingFromToStringTest extends QueueFull
     protected void checkConfig(final LoggerContext ctx) throws ReflectiveOperationException {
         assertAsyncLoggerConfig(ctx, 128);
         assertThat(Constants.isWebApp()).isFalse();
-        assertThat(Constants.isThreadLocalsEnabled()).isTrue();
     }
 }
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
index 16953436b7..5bb6691169 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjectorTest.java
@@ -35,7 +35,6 @@ import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
-import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.LoggingSystem;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
 import org.apache.logging.log4j.spi.ThreadContextMap;
@@ -68,8 +67,7 @@ public class ThreadContextDataInjectorTest {
             {
                 (Function<Boolean, ThreadContextMap>) ThreadContextDataInjectorTest::createGarbageFreeMap,
                 GARBAGE_FREE_MAP_CLASS_NAME
-            },
-            {(Function<Boolean, ThreadContextMap>) ThreadContextDataInjectorTest::createDefaultMap, null}
+            }
         };
     }
 
@@ -169,11 +167,4 @@ public class ThreadContextDataInjectorTest {
         return assertDoesNotThrow(
                 () -> constructor.newInstance(inheritable, LoggingSystem.THREAD_CONTEXT_DEFAULT_INITIAL_CAPACITY));
     }
-
-    private static ThreadContextMap createDefaultMap(final boolean inheritable) {
-        final Constructor<DefaultThreadContextMap> constructor = assertDoesNotThrow(
-                () -> DefaultThreadContextMap.class.getDeclaredConstructor(Boolean.TYPE, Boolean.TYPE));
-        assertTrue(constructor.trySetAccessible(), "Unable to construct DefaultThreadContextMap");
-        return assertDoesNotThrow(() -> constructor.newInstance(true, inheritable));
-    }
 }
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
similarity index 85%
rename from log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java
rename to log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
index d84599d4dd..d908f8013f 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTestBase.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
@@ -25,15 +25,15 @@ import java.util.Date;
 import java.util.TimeZone;
 import org.apache.logging.log4j.core.AbstractLogEvent;
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.NullConfiguration;
 import org.apache.logging.log4j.core.time.Instant;
 import org.apache.logging.log4j.core.time.MutableInstant;
 import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat;
 import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedTimeZoneFormat;
-import org.apache.logging.log4j.util.Constants;
 import org.apache.logging.log4j.util.Strings;
 import org.junit.jupiter.api.Test;
 
-abstract class DatePatternConverterTestBase {
+class DatePatternConverterTest {
 
     private static final class MyLogEvent extends AbstractLogEvent {
 
@@ -77,12 +77,6 @@ abstract class DatePatternConverterTestBase {
 
     private static final String[] ISO8601_FORMAT_OPTIONS = {ISO8601};
 
-    private final boolean threadLocalsEnabled;
-
-    DatePatternConverterTestBase(final boolean threadLocalsEnabled) {
-        this.threadLocalsEnabled = threadLocalsEnabled;
-    }
-
     private static Date date(final int year, final int month, final int date) {
         final Calendar cal = Calendar.getInstance();
         cal.set(year, month, date, 14, 15, 16);
@@ -98,11 +92,6 @@ abstract class DatePatternConverterTestBase {
         return seconds + "nnnnnnnnn".substring(0, precision) + remainder;
     }
 
-    @Test
-    void testThreadLocalsConstant() {
-        assertEquals(threadLocalsEnabled, Constants.isThreadLocalsEnabled());
-    }
-
     @Test
     public void testFormatDateStringBuilderDefaultPattern() {
         assertDatePattern(null, date(2001, 1, 1), "2001-02-01 14:15:16,123");
@@ -110,7 +99,7 @@ abstract class DatePatternConverterTestBase {
 
     @Test
     public void testFormatDateStringBuilderIso8601() {
-        final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
+        final DatePatternConverter converter = createConverter(ISO8601_FORMAT_OPTIONS);
         final StringBuilder sb = new StringBuilder();
         converter.format(date(2001, 1, 1), sb);
 
@@ -133,7 +122,7 @@ abstract class DatePatternConverterTestBase {
     @Test
     public void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() {
         final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD_MICROS.name(), "Z"};
-        final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
+        final DatePatternConverter converter = createConverter(pattern);
         final StringBuilder sb = new StringBuilder();
         final MutableInstant instant = new MutableInstant();
         instant.initFromEpochMilli(
@@ -154,7 +143,7 @@ abstract class DatePatternConverterTestBase {
     @Test
     public void testFormatLogEventStringBuilderDefaultPattern() {
         final LogEvent event = new MyLogEvent();
-        final DatePatternConverter converter = DatePatternConverter.newInstance(null);
+        final DatePatternConverter converter = createConverter(null);
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -165,7 +154,7 @@ abstract class DatePatternConverterTestBase {
     @Test
     public void testFormatLogEventStringBuilderIso8601() {
         final LogEvent event = new MyLogEvent();
-        final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
+        final DatePatternConverter converter = createConverter(ISO8601_FORMAT_OPTIONS);
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -185,7 +174,7 @@ abstract class DatePatternConverterTestBase {
     }
 
     private static void assertDatePattern(final String format, final Date date, final String expected) {
-        final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {format});
+        final DatePatternConverter converter = createConverter(new String[] {format});
         final StringBuilder sb = new StringBuilder();
         converter.format(date, sb);
 
@@ -196,7 +185,7 @@ abstract class DatePatternConverterTestBase {
     public void testFormatLogEventStringBuilderIso8601TimezoneJST() {
         final LogEvent event = new MyLogEvent();
         final String[] optionsWithTimezone = {ISO8601, "JST"};
-        final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+        final DatePatternConverter converter = createConverter(optionsWithTimezone);
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -214,7 +203,7 @@ abstract class DatePatternConverterTestBase {
     public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHCMM() {
         final LogEvent event = new MyLogEvent();
         final String[] optionsWithTimezone = {ISO8601_OFFSET_DATE_TIME_HHCMM};
-        final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+        final DatePatternConverter converter = createConverter(optionsWithTimezone);
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -228,7 +217,7 @@ abstract class DatePatternConverterTestBase {
     public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHMM() {
         final LogEvent event = new MyLogEvent();
         final String[] optionsWithTimezone = {ISO8601_OFFSE_DATE_TIME_HHMM};
-        final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+        final DatePatternConverter converter = createConverter(optionsWithTimezone);
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -241,7 +230,7 @@ abstract class DatePatternConverterTestBase {
     @Test
     public void testFormatLogEventStringBuilderIso8601TimezoneUTC() {
         final LogEvent event = new MyLogEvent();
-        final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {"ISO8601", "UTC"});
+        final DatePatternConverter converter = createConverter(new String[] {"ISO8601", "UTC"});
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -258,7 +247,7 @@ abstract class DatePatternConverterTestBase {
     public void testFormatLogEventStringBuilderIso8601TimezoneZ() {
         final LogEvent event = new MyLogEvent();
         final String[] optionsWithTimezone = {ISO8601, "Z"};
-        final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+        final DatePatternConverter converter = createConverter(optionsWithTimezone);
         final StringBuilder sb = new StringBuilder();
         converter.format(event, sb);
 
@@ -273,7 +262,7 @@ abstract class DatePatternConverterTestBase {
 
     @Test
     public void testFormatObjectStringBuilderDefaultPattern() {
-        final DatePatternConverter converter = DatePatternConverter.newInstance(null);
+        final DatePatternConverter converter = createConverter(null);
         final StringBuilder sb = new StringBuilder();
         converter.format("nondate", sb);
 
@@ -283,7 +272,7 @@ abstract class DatePatternConverterTestBase {
 
     @Test
     public void testFormatStringBuilderObjectArrayDefaultPattern() {
-        final DatePatternConverter converter = DatePatternConverter.newInstance(null);
+        final DatePatternConverter converter = createConverter(null);
         final StringBuilder sb = new StringBuilder();
         converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3));
 
@@ -293,7 +282,7 @@ abstract class DatePatternConverterTestBase {
 
     @Test
     public void testFormatStringBuilderObjectArrayIso8601() {
-        final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
+        final DatePatternConverter converter = createConverter(ISO8601_FORMAT_OPTIONS);
         final StringBuilder sb = new StringBuilder();
         converter.format(sb, date(2001, 1, 1), date(2002, 2, 2), date(2003, 3, 3));
 
@@ -303,43 +292,40 @@ abstract class DatePatternConverterTestBase {
 
     @Test
     public void testGetPatternReturnsDefaultForEmptyOptionsArray() {
-        assertEquals(
-                DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[0]).getPattern());
+        assertEquals(DEFAULT_PATTERN, createConverter(new String[0]).getPattern());
     }
 
     @Test
     public void testGetPatternReturnsDefaultForInvalidPattern() {
         final String[] invalid = {"ABC I am not a valid date pattern"};
-        assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern());
+        assertEquals(DEFAULT_PATTERN, createConverter(invalid).getPattern());
     }
 
     @Test
     public void testGetPatternReturnsDefaultForNullOptions() {
-        assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(null).getPattern());
+        assertEquals(DEFAULT_PATTERN, createConverter(null).getPattern());
     }
 
     @Test
     public void testGetPatternReturnsDefaultForSingleNullElementOptionsArray() {
-        assertEquals(
-                DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[1]).getPattern());
+        assertEquals(DEFAULT_PATTERN, createConverter(new String[1]).getPattern());
     }
 
     @Test
     public void testGetPatternReturnsDefaultForTwoNullElementsOptionsArray() {
-        assertEquals(
-                DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[2]).getPattern());
+        assertEquals(DEFAULT_PATTERN, createConverter(new String[2]).getPattern());
     }
 
     @Test
     public void testGetPatternReturnsNullForUnix() {
         final String[] options = {"UNIX"};
-        assertNull(DatePatternConverter.newInstance(options).getPattern());
+        assertNull(createConverter(options).getPattern());
     }
 
     @Test
     public void testGetPatternReturnsNullForUnixMillis() {
         final String[] options = {"UNIX_MILLIS"};
-        assertNull(DatePatternConverter.newInstance(options).getPattern());
+        assertNull(createConverter(options).getPattern());
     }
 
     @Test
@@ -364,17 +350,17 @@ abstract class DatePatternConverterTestBase {
             final String precisePattern;
             if (foundIndex < 0) {
                 precisePattern = pattern;
-                preciseConverter = DatePatternConverter.newInstance(new String[] {precisePattern});
+                preciseConverter = createConverter(new String[] {precisePattern});
             } else {
                 final String subPattern = pattern.substring(0, foundIndex);
                 final String remainder = pattern.substring(foundIndex + search.length());
                 precisePattern = subPattern + "nnnnnnnnn" + "n" + remainder; // nanos too long
-                preciseConverter = DatePatternConverter.newInstance(new String[] {precisePattern});
+                preciseConverter = createConverter(new String[] {precisePattern});
             }
             preciseConverter.format(event, preciseBuilder);
 
             final String[] milliOptions = {pattern};
-            DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
+            createConverter(milliOptions).format(event, milliBuilder);
             final FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat();
             final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
             final String tz = timeZoneFormat != null
@@ -396,7 +382,7 @@ abstract class DatePatternConverterTestBase {
 
     @Test
     public void testNewInstanceAllowsNullParameter() {
-        DatePatternConverter.newInstance(null); // no errors
+        createConverter(null); // no errors
     }
 
     // test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision)
@@ -423,11 +409,11 @@ abstract class DatePatternConverterTestBase {
 
                     final String precisePattern = precisePattern(pattern, i);
                     final String[] preciseOptions = {precisePattern, timeZone};
-                    final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
+                    final DatePatternConverter preciseConverter = createConverter(preciseOptions);
                     preciseConverter.format(event, preciseBuilder);
 
                     final String[] milliOptions = {pattern, timeZone};
-                    DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
+                    createConverter(milliOptions).format(event, milliBuilder);
                     final FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat();
                     final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
                     final String tz = timeZoneFormat != null
@@ -452,7 +438,7 @@ abstract class DatePatternConverterTestBase {
     public void testPredefinedFormatWithoutTimezone() {
         for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
             final String[] options = {format.name()};
-            final DatePatternConverter converter = DatePatternConverter.newInstance(options);
+            final DatePatternConverter converter = createConverter(options);
             assertEquals(format.getPattern(), converter.getPattern());
         }
     }
@@ -461,8 +447,12 @@ abstract class DatePatternConverterTestBase {
     public void testPredefinedFormatWithTimezone() {
         for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
             final String[] options = {format.name(), "PST"}; // Pacific Standard Time=UTC-8:00
-            final DatePatternConverter converter = DatePatternConverter.newInstance(options);
+            final DatePatternConverter converter = createConverter(options);
             assertEquals(format.getPattern(), converter.getPattern());
         }
     }
+
+    private static DatePatternConverter createConverter(final String[] options) {
+        return DatePatternConverter.newInstance(new NullConfiguration(), options);
+    }
 }
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithThreadLocalsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithThreadLocalsTest.java
deleted file mode 100644
index e632d2785d..0000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithThreadLocalsTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.pattern;
-
-import org.apache.logging.log4j.spi.LoggingSystemProperty;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.apache.logging.log4j.test.junit.UsingTestProperties;
-
-@SetTestProperty(key = LoggingSystemProperty.Constant.WEB_IS_WEBAPP, value = "false")
-@SetTestProperty(key = LoggingSystemProperty.Constant.THREAD_LOCALS_ENABLE, value = "true")
-@UsingTestProperties
-class DatePatternConverterWithThreadLocalsTest extends DatePatternConverterTestBase {
-
-    DatePatternConverterWithThreadLocalsTest() {
-        super(true);
-    }
-}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithoutThreadLocalsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithoutThreadLocalsTest.java
deleted file mode 100644
index d4d667a523..0000000000
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterWithoutThreadLocalsTest.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.pattern;
-
-import org.apache.logging.log4j.spi.LoggingSystemProperty;
-import org.junitpioneer.jupiter.SetSystemProperty;
-
-@SetSystemProperty(key = LoggingSystemProperty.Constant.THREAD_LOCALS_ENABLE, value = "false")
-class DatePatternConverterWithoutThreadLocalsTest extends DatePatternConverterTestBase {
-
-    DatePatternConverterWithoutThreadLocalsTest() {
-        super(false);
-    }
-}
diff --git a/log4j-core-test/src/test/resources/perf/SimplePerfTest.sh b/log4j-core-test/src/test/resources/perf/SimplePerfTest.sh
index e7cbcf2c02..ec877a0829 100644
--- a/log4j-core-test/src/test/resources/perf/SimplePerfTest.sh
+++ b/log4j-core-test/src/test/resources/perf/SimplePerfTest.sh
@@ -37,7 +37,6 @@ GC_OPTIONS=
 
 LOG4J_OPTIONS="-Dlog4j.configurationFile=perf-CountingNoOpAppender.xml"
 LOG4J_OPTIONS="${LOG4J_OPTIONS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
-LOG4J_OPTIONS="${LOG4J_OPTIONS} -Dlog4j2.enable.threadlocals=true"
 LOG4J_OPTIONS="${LOG4J_OPTIONS} -DAsyncLogger.WaitStrategy=Block"
 LOG4J_OPTIONS="${LOG4J_OPTIONS} -Dlog4j.format.msg.async=true"
 export LOG4J_OPTIONS
diff --git a/log4j-core-test/src/test/resources/perf/runResponseTm.sh b/log4j-core-test/src/test/resources/perf/runResponseTm.sh
index a043a30a51..2860dcd9be 100644
--- a/log4j-core-test/src/test/resources/perf/runResponseTm.sh
+++ b/log4j-core-test/src/test/resources/perf/runResponseTm.sh
@@ -40,7 +40,6 @@ LOG4J_OPTIONS="-Dlog4j.configurationFile=perf5AsyncApndNoLoc.xml"
 #LOG4J_OPTIONS="-Dlogback.configurationFile=perf-logback.xml"
 
 #LOG4J_OPTIONS="${LOG4J_OPTIONS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
-LOG4J_OPTIONS="${LOG4J_OPTIONS} -Dlog4j2.enable.threadlocals=true"
 LOG4J_OPTIONS="${LOG4J_OPTIONS} -Dlog4j2.enable.direct.encoders=true"
 LOG4J_OPTIONS="${LOG4J_OPTIONS} -DAsyncLogger.WaitStrategy=busySpin"
 LOG4J_OPTIONS="${LOG4J_OPTIONS} -DAsyncLoggerConfig.WaitStrategy=busySpin"
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java
index 27202729ab..316551eca7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java
@@ -330,8 +330,9 @@ public class DefaultRolloverStrategy extends AbstractRolloverStrategy {
         this.compressionLevel = compressionLevel;
         this.stopCustomActionsOnError = stopCustomActionsOnError;
         this.customActions = customActions == null ? Collections.<Action>emptyList() : Arrays.asList(customActions);
-        this.tempCompressedFilePattern =
-                tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null;
+        this.tempCompressedFilePattern = tempCompressedFilePatternString != null
+                ? new PatternProcessor(configuration, tempCompressedFilePatternString)
+                : null;
     }
 
     public int getCompressionLevel() {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java
index 1d286a61c5..b95c802dcd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java
@@ -101,7 +101,8 @@ public class DirectWriteRolloverStrategy extends AbstractRolloverStrategy implem
                     config.getStrSubstitutor(),
                     customActions,
                     stopCustomActionsOnError,
-                    tempCompressedFilePattern);
+                    tempCompressedFilePattern,
+                    config);
         }
 
         public String getMaxFiles() {
@@ -227,14 +228,16 @@ public class DirectWriteRolloverStrategy extends AbstractRolloverStrategy implem
             final StrSubstitutor strSubstitutor,
             final Action[] customActions,
             final boolean stopCustomActionsOnError,
-            final String tempCompressedFilePatternString) {
+            final String tempCompressedFilePatternString,
+            final Configuration configuration) {
         super(strSubstitutor);
         this.maxFiles = maxFiles;
         this.compressionLevel = compressionLevel;
         this.stopCustomActionsOnError = stopCustomActionsOnError;
         this.customActions = customActions == null ? Collections.<Action>emptyList() : Arrays.asList(customActions);
-        this.tempCompressedFilePattern =
-                tempCompressedFilePatternString != null ? new PatternProcessor(tempCompressedFilePatternString) : null;
+        this.tempCompressedFilePattern = tempCompressedFilePatternString != null
+                ? new PatternProcessor(configuration, tempCompressedFilePatternString)
+                : null;
     }
 
     public int getCompressionLevel() {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
index a31d8e73cc..05dd3175c1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
@@ -23,6 +23,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.lookup.StrSubstitutor;
 import org.apache.logging.log4j.core.pattern.ArrayPatternConverter;
 import org.apache.logging.log4j.core.pattern.DatePatternConverter;
@@ -77,9 +78,9 @@ public class PatternProcessor {
      * Constructor.
      * @param pattern The file pattern.
      */
-    public PatternProcessor(final String pattern) {
+    public PatternProcessor(final Configuration configuration, final String pattern) {
         this.pattern = pattern;
-        final PatternParser parser = new PatternParser(null, KEY, ArrayPatternConverter.class);
+        final PatternParser parser = new PatternParser(configuration, KEY, ArrayPatternConverter.class);
         final List<PatternConverter> converters = new ArrayList<>();
         final List<FormattingInfo> fields = new ArrayList<>();
         parser.parse(pattern, converters, fields, false, false, false);
@@ -108,8 +109,8 @@ public class PatternProcessor {
      * @param pattern  The file pattern.
      * @param copy Source pattern processor
      */
-    public PatternProcessor(final String pattern, final PatternProcessor copy) {
-        this(pattern);
+    public PatternProcessor(final Configuration configuration, final String pattern, final PatternProcessor copy) {
+        this(configuration, pattern);
         this.prevFileTime = copy.prevFileTime;
         this.nextFileTime = copy.nextFileTime;
         this.currentFileTime = copy.currentFileTime;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
index 28d38eefc0..c613fcf5b3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
@@ -92,6 +92,7 @@ public class RollingFileManager extends FileManager {
      * @since 2.9
      */
     protected RollingFileManager(
+            final Configuration configuration,
             final LoggerContext loggerContext,
             final String fileName,
             final String pattern,
@@ -127,7 +128,7 @@ public class RollingFileManager extends FileManager {
         this.initialTime = initialTime;
         this.triggeringPolicy = triggeringPolicy;
         this.rolloverStrategy = rolloverStrategy;
-        this.patternProcessor = new PatternProcessor(pattern);
+        this.patternProcessor = new PatternProcessor(configuration, pattern);
         this.patternProcessor.setPrevFileTime(initialTime);
         this.fileName = fileName;
         this.directWrite = rolloverStrategy instanceof DirectFileRolloverStrategy;
@@ -744,7 +745,8 @@ public class RollingFileManager extends FileManager {
     public void updateData(final Object data) {
         final FactoryData factoryData = (FactoryData) data;
         setRolloverStrategy(factoryData.getRolloverStrategy());
-        setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
+        setPatternProcessor(
+                new PatternProcessor(factoryData.getConfiguration(), factoryData.getPattern(), getPatternProcessor()));
         setTriggeringPolicy(factoryData.getTriggeringPolicy());
     }
 
@@ -791,6 +793,7 @@ public class RollingFileManager extends FileManager {
                 final boolean writeHeader = file != null && file.exists() && file.length() == 0;
 
                 final RollingFileManager rm = new RollingFileManager(
+                        data.getConfiguration(),
                         data.getLoggerContext(),
                         data.fileName,
                         data.pattern,
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java
index 61fff7da04..05a56cc99a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java
@@ -49,6 +49,7 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
      * @since 2.8.3
      */
     public RollingRandomAccessFileManager(
+            final Configuration configuration,
             final LoggerContext loggerContext,
             final RandomAccessFile raf,
             final String fileName,
@@ -68,6 +69,7 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
             final String fileGroup,
             final boolean writeHeader) {
         super(
+                configuration,
                 loggerContext,
                 fileName,
                 pattern,
@@ -296,6 +298,7 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
             final boolean writeHeader = !data.append || file == null || !file.exists();
 
             final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(
+                    data.getConfiguration(),
                     data.getLoggerContext(),
                     raf,
                     name,
@@ -407,7 +410,8 @@ public class RollingRandomAccessFileManager extends RollingFileManager {
     public void updateData(final Object data) {
         final FactoryData factoryData = (FactoryData) data;
         setRolloverStrategy(factoryData.getRolloverStrategy());
-        setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
+        setPatternProcessor(
+                new PatternProcessor(factoryData.getConfiguration(), factoryData.getPattern(), getPatternProcessor()));
         setTriggeringPolicy(factoryData.getTriggeringPolicy());
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
index 76eb979e29..4aef03e08b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
@@ -17,7 +17,6 @@
 package org.apache.logging.log4j.core.async;
 
 import com.lmax.disruptor.EventTranslatorVararg;
-import com.lmax.disruptor.dsl.Disruptor;
 import java.util.List;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
@@ -35,9 +34,8 @@ import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.ReusableMessage;
 import org.apache.logging.log4j.spi.AbstractLogger;
-import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.StringMap;
 
@@ -66,13 +64,10 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
     // this is within the MaxInlineSize threshold and makes these methods candidates for
     // immediate inlining instead of waiting until they are designated "hot enough".
 
-    private static final StatusLogger LOGGER = StatusLogger.getLogger();
     private final Clock clock; // not reconfigurable
     private final ContextDataInjector contextDataInjector; // not reconfigurable
 
-    private static final ThreadNameCachingStrategy THREAD_NAME_CACHING_STRATEGY = ThreadNameCachingStrategy.create();
-
-    private final ThreadLocal<RingBufferLogEventTranslator> threadLocalTranslator = new ThreadLocal<>();
+    private final Recycler<RingBufferLogEventTranslator> translatorRecycler;
     private final AsyncLoggerDisruptor loggerDisruptor;
 
     private volatile boolean includeLocation; // reconfigurable
@@ -92,9 +87,12 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
             final MessageFactory messageFactory,
             final AsyncLoggerDisruptor loggerDisruptor) {
         super(context, name, messageFactory);
+        final Configuration configuration = context.getConfiguration();
+        this.translatorRecycler = configuration
+                .getRecyclerFactory()
+                .create(RingBufferLogEventTranslator::new, RingBufferLogEventTranslator::clear);
         this.loggerDisruptor = loggerDisruptor;
         includeLocation = privateConfig.loggerConfig.isIncludeLocation();
-        final Configuration configuration = context.getConfiguration();
         nanoClock = configuration.getNanoClock();
         clock = configuration.getComponent(Clock.KEY);
         contextDataInjector = configuration.getComponent(ContextDataInjector.KEY);
@@ -117,107 +115,6 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
         return nanoClock;
     }
 
-    private RingBufferLogEventTranslator getCachedTranslator() {
-        RingBufferLogEventTranslator result = threadLocalTranslator.get();
-        if (result == null) {
-            result = new RingBufferLogEventTranslator();
-            threadLocalTranslator.set(result);
-        }
-        return result;
-    }
-
-    @Override
-    public void logMessage(
-            final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) {
-        getTranslatorType().log(fqcn, level, marker, message, thrown);
-    }
-
-    @Override
-    public void log(
-            final Level level,
-            final Marker marker,
-            final String fqcn,
-            final StackTraceElement location,
-            final Message message,
-            final Throwable throwable) {
-        getTranslatorType().log(fqcn, location, level, marker, message, throwable);
-    }
-
-    abstract class TranslatorType {
-
-        abstract void log(
-                final String fqcn,
-                final StackTraceElement location,
-                final Level level,
-                final Marker marker,
-                final Message message,
-                final Throwable thrown);
-
-        abstract void log(
-                final String fqcn,
-                final Level level,
-                final Marker marker,
-                final Message message,
-                final Throwable thrown);
-    }
-
-    private final TranslatorType threadLocalTranslatorType = new TranslatorType() {
-
-        @Override
-        void log(
-                final String fqcn,
-                final StackTraceElement location,
-                final Level level,
-                final Marker marker,
-                final Message message,
-                final Throwable thrown) {
-            logWithThreadLocalTranslator(fqcn, location, level, marker, message, thrown);
-        }
-
-        @Override
-        void log(
-                final String fqcn,
-                final Level level,
-                final Marker marker,
-                final Message message,
-                final Throwable thrown) {
-            logWithThreadLocalTranslator(fqcn, level, marker, message, thrown);
-        }
-    };
-
-    private final TranslatorType varargTranslatorType = new TranslatorType() {
-
-        @Override
-        void log(
-                final String fqcn,
-                final StackTraceElement location,
-                final Level level,
-                final Marker marker,
-                final Message message,
-                final Throwable thrown) {
-            logWithVarargTranslator(fqcn, location, level, marker, message, thrown);
-        }
-
-        @Override
-        void log(
-                final String fqcn,
-                final Level level,
-                final Marker marker,
-                final Message message,
-                final Throwable thrown) {
-            // LOG4J2-1172: avoid storing non-JDK classes in ThreadLocals to avoid memory leaks in web apps
-            logWithVarargTranslator(fqcn, level, marker, message, thrown);
-        }
-    };
-
-    private TranslatorType getTranslatorType() {
-        return loggerDisruptor.isUseThreadLocals() ? threadLocalTranslatorType : varargTranslatorType;
-    }
-
-    private boolean isReused(final Message message) {
-        return message instanceof ReusableMessage;
-    }
-
     /**
      * Enqueues the specified log event data for logging in a background thread.
      * <p>
@@ -230,14 +127,19 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
      * @param message the log message
      * @param thrown a {@code Throwable} or {@code null}
      */
-    private void logWithThreadLocalTranslator(
+    @Override
+    public void logMessage(
             final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) {
         // Implementation note: this method is tuned for performance. MODIFY WITH CARE!
 
-        final RingBufferLogEventTranslator translator = getCachedTranslator();
-        initTranslator(translator, fqcn, level, marker, message, thrown);
-        initTranslatorThreadValues(translator);
-        publish(translator);
+        final RingBufferLogEventTranslator translator = translatorRecycler.acquire();
+        try {
+            initTranslator(translator, fqcn, level, marker, message, thrown);
+            translator.updateThreadValues();
+            publish(translator);
+        } finally {
+            translatorRecycler.release(translator);
+        }
     }
 
     /**
@@ -253,23 +155,23 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
      * @param message the log message
      * @param thrown a {@code Throwable} or {@code null}
      */
-    private void logWithThreadLocalTranslator(
-            final String fqcn,
-            final StackTraceElement location,
+    @Override
+    public void log(
             final Level level,
             final Marker marker,
+            final String fqcn,
+            final StackTraceElement location,
             final Message message,
             final Throwable thrown) {
         // Implementation note: this method is tuned for performance. MODIFY WITH CARE!
 
-        final RingBufferLogEventTranslator translator = getCachedTranslator();
-        initTranslator(translator, fqcn, location, level, marker, message, thrown);
-        initTranslatorThreadValues(translator);
+        final RingBufferLogEventTranslator translator = translatorRecycler.acquire();
         try {
+            initTranslator(translator, fqcn, location, level, marker, message, thrown);
+            translator.updateThreadValues();
             publish(translator);
-        } catch (Throwable ex) {
-            translator.clear();
-            throw ex;
+        } finally {
+            translatorRecycler.release(translator);
         }
     }
 
@@ -361,13 +263,6 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
                 contextDataInjector);
     }
 
-    private void initTranslatorThreadValues(final RingBufferLogEventTranslator translator) {
-        // constant check should be optimized out when using default (CACHED)
-        if (THREAD_NAME_CACHING_STRATEGY == ThreadNameCachingStrategy.UNCACHED) {
-            translator.updateThreadValues();
-        }
-    }
-
     /**
      * Returns the caller location if requested, {@code null} otherwise.
      *
@@ -381,94 +276,6 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
         return includeLocation ? StackLocatorUtil.calcLocation(fqcn) : null;
     }
 
-    /**
-     * Enqueues the specified log event data for logging in a background thread.
-     * <p>
-     * This creates a new varargs Object array for each invocation, but does not store any non-JDK classes in a
-     * {@code ThreadLocal} to avoid memory leaks in web applications (see LOG4J2-1172).
-     *
-     * @param fqcn fully qualified name of the caller
-     * @param level level at which the caller wants to log the message
-     * @param marker message marker
-     * @param message the log message
-     * @param thrown a {@code Throwable} or {@code null}
-     */
-    private void logWithVarargTranslator(
-            final String fqcn, final Level level, final Marker marker, final Message message, final Throwable thrown) {
-        // Implementation note: candidate for optimization: exceeds 35 bytecodes.
-
-        final Disruptor<RingBufferLogEvent> disruptor = loggerDisruptor.getDisruptor();
-        if (disruptor == null) {
-            LOGGER.error("Ignoring log event after Log4j has been shut down.");
-            return;
-        }
-        // if the Message instance is reused, there is no point in freezing its message here
-        if (!isReused(message)) {
-            InternalAsyncUtil.makeMessageImmutable(message);
-        }
-        StackTraceElement location = null;
-        // calls the translateTo method on this AsyncLogger
-        if (!disruptor
-                .getRingBuffer()
-                .tryPublishEvent(
-                        this,
-                        this, // asyncLogger: 0
-                        (location = calcLocationIfRequested(fqcn)), // location: 1
-                        fqcn, // 2
-                        level, // 3
-                        marker, // 4
-                        message, // 5
-                        thrown)) { // 6
-            handleRingBufferFull(location, fqcn, level, marker, message, thrown);
-        }
-    }
-
-    /**
-     * Enqueues the specified log event data for logging in a background thread.
-     * <p>
-     * This creates a new varargs Object array for each invocation, but does not store any non-JDK classes in a
-     * {@code ThreadLocal} to avoid memory leaks in web applications (see LOG4J2-1172).
-     *
-     * @param fqcn fully qualified name of the caller
-     * @param location location of the caller.
-     * @param level level at which the caller wants to log the message
-     * @param marker message marker
-     * @param message the log message
-     * @param thrown a {@code Throwable} or {@code null}
-     */
-    private void logWithVarargTranslator(
-            final String fqcn,
-            final StackTraceElement location,
-            final Level level,
-            final Marker marker,
-            final Message message,
-            final Throwable thrown) {
-        // Implementation note: candidate for optimization: exceeds 35 bytecodes.
-
-        final Disruptor<RingBufferLogEvent> disruptor = loggerDisruptor.getDisruptor();
-        if (disruptor == null) {
-            LOGGER.error("Ignoring log event after Log4j has been shut down.");
-            return;
-        }
-        // if the Message instance is reused, there is no point in freezing its message here
-        if (!isReused(message)) {
-            InternalAsyncUtil.makeMessageImmutable(message);
-        }
-        // calls the translateTo method on this AsyncLogger
-        if (!disruptor
-                .getRingBuffer()
-                .tryPublishEvent(
-                        this, this, // asyncLogger: 0
-                        location, // location: 1
-                        fqcn, // 2
-                        level, // 3
-                        marker, // 4
-                        message, // 5
-                        thrown)) { // 6
-            handleRingBufferFull(location, fqcn, level, marker, message, thrown);
-        }
-    }
-
     /*
      * (non-Javadoc)
      *
@@ -489,7 +296,6 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
         final ContextStack contextStack = ThreadContext.getImmutableStack();
 
         final Thread currentThread = Thread.currentThread();
-        final String threadName = THREAD_NAME_CACHING_STRATEGY.getThreadName();
         event.setValues(
                 asyncLogger,
                 asyncLogger.getName(),
@@ -503,7 +309,7 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
                 contextDataInjector.injectContextData(null, (StringMap) event.getContextData()),
                 contextStack,
                 currentThread.getId(),
-                threadName,
+                currentThread.getName(),
                 currentThread.getPriority(),
                 location,
                 clock,
@@ -527,41 +333,6 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
         strategy.log(this, getName(), fqcn, marker, level, message, thrown);
     }
 
-    private void handleRingBufferFull(
-            final StackTraceElement location,
-            final String fqcn,
-            final Level level,
-            final Marker marker,
-            final Message msg,
-            final Throwable thrown) {
-        if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
-            // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock
-            AsyncQueueFullMessageUtil.logWarningToStatusLogger();
-            logMessageInCurrentThread(fqcn, level, marker, msg, thrown);
-            return;
-        }
-        final EventRoute eventRoute = loggerDisruptor.getEventRoute(level);
-        switch (eventRoute) {
-            case ENQUEUE:
-                loggerDisruptor.enqueueLogMessageWhenQueueFull(
-                        this, this, // asyncLogger: 0
-                        location, // location: 1
-                        fqcn, // 2
-                        level, // 3
-                        marker, // 4
-                        msg, // 5
-                        thrown); // 6
-                break;
-            case SYNCHRONOUS:
-                logMessageInCurrentThread(fqcn, level, marker, msg, thrown);
-                break;
-            case DISCARD:
-                break;
-            default:
-                throw new IllegalStateException("Unknown EventRoute " + eventRoute);
-        }
-    }
-
     /**
      * This method is called by the EventHandler that processes the RingBufferLogEvent in a separate thread.
      * Merges the contents of the configuration map into the contextData, after replacing any variables in the property
@@ -597,7 +368,7 @@ public class AsyncLogger extends Logger implements EventTranslatorVararg<RingBuf
     }
 
     private static StringMap getContextData(final RingBufferLogEvent event) {
-        final StringMap contextData = (StringMap) event.getContextData();
+        final StringMap contextData = event.getContextData();
         if (contextData.isFrozen()) {
             final StringMap temp = ContextDataFactory.createContextData();
             temp.putAll(contextData);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java
index f9c964d312..9f6389081d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerContext.java
@@ -140,12 +140,4 @@ public class AsyncLoggerContext extends LoggerContext {
     public RingBufferAdmin createRingBufferAdmin() {
         return loggerDisruptor.createRingBufferAdmin(getName());
     }
-
-    /**
-     * Signals this context whether it is allowed to use ThreadLocal objects for efficiency.
-     * @param useThreadLocals whether this context is allowed to use ThreadLocal objects
-     */
-    public void setUseThreadLocals(final boolean useThreadLocals) {
-        loggerDisruptor.setUseThreadLocals(useThreadLocals);
-    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java
index c739918fc7..fb47790187 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java
@@ -16,7 +16,6 @@
  */
 package org.apache.logging.log4j.core.async;
 
-import com.lmax.disruptor.EventTranslatorVararg;
 import com.lmax.disruptor.ExceptionHandler;
 import com.lmax.disruptor.RingBuffer;
 import com.lmax.disruptor.TimeoutException;
@@ -30,7 +29,6 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
 import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.core.AbstractLifeCycle;
 import org.apache.logging.log4j.core.impl.Log4jPropertyKey;
 import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
@@ -56,10 +54,8 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle {
     private String contextName;
     private final Supplier<AsyncWaitStrategyFactory> waitStrategyFactorySupplier;
 
-    private boolean useThreadLocalTranslator = true;
     private long backgroundThreadId;
     private AsyncQueueFullPolicy asyncQueueFullPolicy;
-    private int ringBufferSize;
     private WaitStrategy waitStrategy;
 
     AsyncLoggerDisruptor(
@@ -107,7 +103,7 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle {
             }
             setStarting();
             LOGGER.trace("[{}] AsyncLoggerDisruptor creating new disruptor for this context.", contextName);
-            ringBufferSize = DisruptorUtil.calculateRingBufferSize(Log4jPropertyKey.ASYNC_LOGGER_RING_BUFFER_SIZE);
+            int ringBufferSize = DisruptorUtil.calculateRingBufferSize(Log4jPropertyKey.ASYNC_LOGGER_RING_BUFFER_SIZE);
             final AsyncWaitStrategyFactory factory =
                     waitStrategyFactorySupplier.get(); // get factory from configuration
             waitStrategy = DisruptorUtil.createWaitStrategy(Log4jPropertyKey.ASYNC_LOGGER_WAIT_STRATEGY, factory);
@@ -141,10 +137,6 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle {
                     errorHandler);
             disruptor.start();
 
-            LOGGER.trace(
-                    "[{}] AsyncLoggers use a {} translator",
-                    contextName,
-                    useThreadLocalTranslator ? "threadlocal" : "vararg");
             super.start();
         } finally {
             startLock.unlock();
@@ -276,55 +268,6 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle {
         }
     }
 
-    void enqueueLogMessageWhenQueueFull(
-            final EventTranslatorVararg<RingBufferLogEvent> translator,
-            final AsyncLogger asyncLogger,
-            final StackTraceElement location,
-            final String fqcn,
-            final Level level,
-            final Marker marker,
-            final Message msg,
-            final Throwable thrown) {
-        try {
-            // Note: we deliberately access the volatile disruptor field afresh here.
-            // Avoiding this and using an older reference could result in adding a log event to the disruptor after it
-            // was shut down, which could cause the publishEvent method to hang and never return.
-            if (synchronizeEnqueueWhenQueueFull()) {
-                queueFullEnqueueLock.lock();
-                try {
-                    disruptor
-                            .getRingBuffer()
-                            .publishEvent(
-                                    translator,
-                                    asyncLogger, // asyncLogger: 0
-                                    location, // location: 1
-                                    fqcn, // 2
-                                    level, // 3
-                                    marker, // 4
-                                    msg, // 5
-                                    thrown); // 6
-                } finally {
-                    queueFullEnqueueLock.unlock();
-                }
-            } else {
-                disruptor
-                        .getRingBuffer()
-                        .publishEvent(
-                                translator,
-                                asyncLogger, // asyncLogger: 0
-                                location, // location: 1
-                                fqcn, // 2
-                                level, // 3
-                                marker, // 4
-                                msg, // 5
-                                thrown); // 6
-            }
-        } catch (final NullPointerException npe) {
-            // LOG4J2-639: catch NPE if disruptor field was set to null in stop()
-            logWarningOnNpeFromDisruptorPublish(level, fqcn, msg, thrown);
-        }
-    }
-
     private boolean synchronizeEnqueueWhenQueueFull() {
         return DisruptorUtil.ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
                 // Background thread must never block
@@ -351,34 +294,4 @@ class AsyncLoggerDisruptor extends AbstractLifeCycle {
                 msg.getFormattedMessage(),
                 thrown == null ? "" : Throwables.toStringList(thrown));
     }
-
-    /**
-     * Returns whether it is allowed to store non-JDK classes in ThreadLocal objects for efficiency.
-     *
-     * @return whether AsyncLoggers are allowed to use ThreadLocal objects
-     * @since 2.5
-     * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1172">LOG4J2-1172</a>
-     */
-    public boolean isUseThreadLocals() {
-        return useThreadLocalTranslator;
-    }
-
-    /**
-     * Signals this AsyncLoggerDisruptor whether it is allowed to store non-JDK classes in ThreadLocal objects for
-     * efficiency.
-     * <p>
-     * This property may be modified after the {@link #start()} method has been called.
-     * </p>
-     *
-     * @param allow whether AsyncLoggers are allowed to use ThreadLocal objects
-     * @since 2.5
-     * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-1172">LOG4J2-1172</a>
-     */
-    public void setUseThreadLocals(final boolean allow) {
-        useThreadLocalTranslator = allow;
-        LOGGER.trace(
-                "[{}] AsyncLoggers have been modified to use a {} translator",
-                contextName,
-                useThreadLocalTranslator ? "threadlocal" : "vararg");
-    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
index b191f9a65d..7d9444e0ef 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
@@ -18,7 +18,6 @@ package org.apache.logging.log4j.core.async;
 
 import static org.apache.logging.log4j.core.impl.Log4jPropertyKey.ASYNC_CONFIG_EXCEPTION_HANDLER_CLASS_NAME;
 import static org.apache.logging.log4j.core.impl.Log4jPropertyKey.ASYNC_LOGGER_EXCEPTION_HANDLER_CLASS_NAME;
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
 
 import com.lmax.disruptor.ExceptionHandler;
 import com.lmax.disruptor.WaitStrategy;
@@ -38,8 +37,7 @@ import org.apache.logging.log4j.util.PropertyKey;
 final class DisruptorUtil {
     private static final Logger LOGGER = StatusLogger.getLogger();
     private static final int RINGBUFFER_MIN_SIZE = 128;
-    private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
-    private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024;
+    private static final int RINGBUFFER_DEFAULT_SIZE = 4 * 1024;
 
     /**
      * LOG4J2-2606: Users encountered excessive CPU utilization with Disruptor v3.4.2 when the application
@@ -69,7 +67,7 @@ final class DisruptorUtil {
     }
 
     static int calculateRingBufferSize(final PropertyKey key) {
-        int ringBufferSize = isThreadLocalsEnabled() ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE;
+        int ringBufferSize = RINGBUFFER_DEFAULT_SIZE;
         final String userPreferredRBSize =
                 PropertiesUtil.getProperties().getStringProperty(key, String.valueOf(ringBufferSize));
         try {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
index 6a8f048d16..4b2f1c46e1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEvent.java
@@ -16,8 +16,6 @@
  */
 package org.apache.logging.log4j.core.async;
 
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
-
 import com.lmax.disruptor.EventFactory;
 import java.util.Arrays;
 import org.apache.logging.log4j.Level;
@@ -498,18 +496,10 @@ public class RingBufferLogEvent implements ReusableLogEvent, ReusableMessage, Ch
         }
 
         // ensure that excessively long char[] arrays are not kept in memory forever
-        if (isThreadLocalsEnabled()) {
-            StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
+        StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
 
-            if (parameters != null) {
-                Arrays.fill(parameters, null);
-            }
-        } else {
-            // A user may have manually logged a ReusableMessage implementation, when thread locals are
-            // disabled we remove the reference in order to avoid permanently holding references to these
-            // buffers.
-            messageText = null;
-            parameters = null;
+        if (parameters != null) {
+            Arrays.fill(parameters, null);
         }
     }
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java
deleted file mode 100644
index bf4a9ed51b..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ThreadNameCachingStrategy.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.core.async;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.logging.log4j.core.impl.Log4jPropertyKey;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Constants;
-import org.apache.logging.log4j.util.PropertiesUtil;
-
-/**
- * Strategy for deciding whether thread name should be cached or not.
- */
-public enum ThreadNameCachingStrategy { // LOG4J2-467
-    CACHED {
-        @Override
-        public String getThreadName() {
-            String result = THREADLOCAL_NAME.get();
-            if (result == null) {
-                result = Thread.currentThread().getName();
-                THREADLOCAL_NAME.set(result);
-            }
-            return result;
-        }
-    },
-    UNCACHED {
-        @Override
-        public String getThreadName() {
-            return Thread.currentThread().getName();
-        }
-    };
-
-    private static final StatusLogger LOGGER = StatusLogger.getLogger();
-    private static final ThreadLocal<String> THREADLOCAL_NAME = new ThreadLocal<>();
-    static final ThreadNameCachingStrategy DEFAULT_STRATEGY = isAllocatingThreadGetName() ? CACHED : UNCACHED;
-
-    abstract String getThreadName();
-
-    public static ThreadNameCachingStrategy create() {
-        final String name =
-                PropertiesUtil.getProperties().getStringProperty(Log4jPropertyKey.ASYNC_LOGGER_THREAD_NAME_STRATEGY);
-        try {
-            final ThreadNameCachingStrategy result =
-                    name != null ? ThreadNameCachingStrategy.valueOf(name) : DEFAULT_STRATEGY;
-            LOGGER.debug(
-                    "{}={} (user specified {}, default is {})",
-                    Log4jPropertyKey.ASYNC_LOGGER_THREAD_NAME_STRATEGY,
-                    result.name(),
-                    name,
-                    DEFAULT_STRATEGY.name());
-            return result;
-        } catch (final Exception ex) {
-            LOGGER.debug(
-                    "Using {}.{}: '{}' not valid: {}",
-                    Log4jPropertyKey.ASYNC_LOGGER_THREAD_NAME_STRATEGY,
-                    DEFAULT_STRATEGY.name(),
-                    name,
-                    ex.toString());
-            return DEFAULT_STRATEGY;
-        }
-    }
-
-    static boolean isAllocatingThreadGetName() {
-        // LOG4J2-2052, LOG4J2-2635 JDK 8u102 ("1.8.0_102") removed the String allocation in Thread.getName()
-        if (Constants.JAVA_MAJOR_VERSION == 8) {
-            try {
-                final Pattern javaVersionPattern = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)_(\\d+)");
-                final Matcher m = javaVersionPattern.matcher(System.getProperty("java.version"));
-                if (m.matches()) {
-                    return Integer.parseInt(m.group(3)) == 0 && Integer.parseInt(m.group(4)) < 102;
-                }
-                return true;
-            } catch (final Exception e) {
-                return true;
-            }
-        } else {
-            return Constants.JAVA_MAJOR_VERSION < 8;
-        }
-    }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java
index 13e16a8c87..6a358a9345 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java
@@ -20,7 +20,6 @@ import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.spi.CopyOnWrite;
-import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.LoaderUtil;
@@ -80,7 +79,7 @@ public class ContextDataInjectorFactory {
         final ReadOnlyThreadContextMap threadContextMap = ThreadContext.getThreadContextMap();
 
         // note: map may be null (if legacy custom ThreadContextMap was installed by user)
-        if (threadContextMap instanceof DefaultThreadContextMap || threadContextMap == null) {
+        if (threadContextMap == null) {
             return new ThreadContextDataInjector.ForDefaultThreadContextMap(); // for non StringMap-based context maps
         }
         if (threadContextMap instanceof CopyOnWrite) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
index 7d9091071b..79ba0674dd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultBundle.java
@@ -16,8 +16,6 @@
  */
 package org.apache.logging.log4j.core.impl;
 
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
-
 import java.util.Map;
 import java.util.function.Supplier;
 import org.apache.logging.log4j.Level;
@@ -91,7 +89,7 @@ public class DefaultBundle {
         final ReadOnlyThreadContextMap threadContextMap = ThreadContext.getThreadContextMap();
 
         // note: map may be null (if legacy custom ThreadContextMap was installed by user)
-        if (threadContextMap instanceof DefaultThreadContextMap || threadContextMap == null) {
+        if (threadContextMap == null) {
             // for non StringMap-based context maps
             return new ThreadContextDataInjector.ForDefaultThreadContextMap();
         }
@@ -103,14 +101,12 @@ public class DefaultBundle {
 
     @SingletonFactory
     @ConditionalOnMissingBinding
-    public LogEventFactory defaultLogEventFactory(
+    public LogEventFactory reusableLogEventFactory(
             final ContextDataInjector injector,
             final Clock clock,
             final NanoClock nanoClock,
             final RecyclerFactory recyclerFactory) {
-        return isThreadLocalsEnabled()
-                ? new ReusableLogEventFactory(injector, clock, nanoClock, recyclerFactory)
-                : new DefaultLogEventFactory(injector, clock, nanoClock);
+        return new ReusableLogEventFactory(injector, clock, nanoClock, recyclerFactory);
     }
 
     @SingletonFactory
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
index 6f40131165..7a19db87b3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
@@ -23,7 +23,6 @@ import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.ContextDataInjector;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.ReusableLogEvent;
-import org.apache.logging.log4j.core.async.ThreadNameCachingStrategy;
 import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.NanoClock;
@@ -38,7 +37,6 @@ import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
  * @see Recycler
  */
 public class ReusableLogEventFactory implements LogEventFactory {
-    private static final ThreadNameCachingStrategy THREAD_NAME_CACHING_STRATEGY = ThreadNameCachingStrategy.create();
 
     private final ContextDataInjector injector;
     private final Clock clock;
@@ -125,11 +123,8 @@ public class ReusableLogEventFactory implements LogEventFactory {
         result.setContextData(injector.injectContextData(properties, result.getContextData()));
         result.setContextStack(
                 ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : ThreadContext.cloneStack()); // mutable copy
-
-        if (THREAD_NAME_CACHING_STRATEGY == ThreadNameCachingStrategy.UNCACHED) {
-            result.setThreadName(Thread.currentThread().getName()); // Thread.getName() allocates Objects on each call
-            result.setThreadPriority(Thread.currentThread().getPriority());
-        }
+        result.setThreadName(Thread.currentThread().getName());
+        result.setThreadPriority(Thread.currentThread().getPriority());
         return result;
     }
 
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
index f0df168b0c..42fdbd10eb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
@@ -132,7 +132,7 @@ public final class HtmlLayout extends AbstractStringLayout {
         this.headerSize = headerSize;
         this.datePatternConverter = DEFAULT_DATE_PATTERN.equals(datePattern)
                 ? null
-                : DatePatternConverter.newInstance(new String[] {datePattern, timezone});
+                : DatePatternConverter.newInstance(configuration, new String[] {datePattern, timezone});
     }
 
     /**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index e77c9aa599..d923c34f7c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -16,15 +16,13 @@
  */
 package org.apache.logging.log4j.core.pattern;
 
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
-
 import java.util.Arrays;
 import java.util.Date;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicReference;
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.time.Instant;
 import org.apache.logging.log4j.core.time.MutableInstant;
 import org.apache.logging.log4j.core.time.internal.format.FastDateFormat;
@@ -32,6 +30,7 @@ import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat;
 import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedFormat;
 import org.apache.logging.log4j.plugins.Namespace;
 import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.PerformanceSensitive;
 
 /**
@@ -47,8 +46,6 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         long previousTime; // for ThreadLocal caching mode
         int nanos;
 
-        abstract String format(final Instant instant);
-
         abstract void formatToBuffer(final Instant instant, StringBuilder destination);
 
         public String toPattern() {
@@ -70,11 +67,6 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
             this.fastDateFormat = fastDateFormat;
         }
 
-        @Override
-        String format(final Instant instant) {
-            return fastDateFormat.format(instant.getEpochMillisecond());
-        }
-
         @Override
         void formatToBuffer(final Instant instant, final StringBuilder destination) {
             final long timeMillis = instant.getEpochMillisecond();
@@ -107,11 +99,6 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
             this.fixedDateFormat = fixedDateFormat;
         }
 
-        @Override
-        String format(final Instant instant) {
-            return fixedDateFormat.formatInstant(instant);
-        }
-
         @Override
         void formatToBuffer(final Instant instant, final StringBuilder destination) {
             final long epochSecond = instant.getEpochSecond();
@@ -137,11 +124,6 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
 
     private static final class UnixFormatter extends Formatter {
 
-        @Override
-        String format(final Instant instant) {
-            return Long.toString(instant.getEpochSecond());
-        }
-
         @Override
         void formatToBuffer(final Instant instant, final StringBuilder destination) {
             destination.append(instant.getEpochSecond()); // no need for caching
@@ -150,29 +132,12 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
 
     private static final class UnixMillisFormatter extends Formatter {
 
-        @Override
-        String format(final Instant instant) {
-            return Long.toString(instant.getEpochMillisecond());
-        }
-
         @Override
         void formatToBuffer(final Instant instant, final StringBuilder destination) {
             destination.append(instant.getEpochMillisecond()); // no need for caching
         }
     }
 
-    private final class CachedTime {
-        public final long epochSecond;
-        public final int nanoOfSecond;
-        public final String formatted;
-
-        public CachedTime(final Instant instant) {
-            this.epochSecond = instant.getEpochSecond();
-            this.nanoOfSecond = instant.getNanoOfSecond();
-            this.formatted = formatter.format(instant);
-        }
-    }
-
     /**
      * UNIX formatter in seconds (standard).
      */
@@ -183,28 +148,31 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
      */
     private static final String UNIX_MILLIS_FORMAT = "UNIX_MILLIS";
 
-    private final String[] options;
-    private final ThreadLocal<MutableInstant> threadLocalMutableInstant = new ThreadLocal<>();
-    private final ThreadLocal<Formatter> threadLocalFormatter = new ThreadLocal<>();
-    private final AtomicReference<CachedTime> cachedTime;
-    private final Formatter formatter;
+    private final String pattern;
+
+    private final TimeZone timeZone;
+
+    private final Recycler<MutableInstant> mutableInstantRecycler;
+
+    private final Recycler<Formatter> formatterRecycler;
 
     /**
      * Private constructor.
      *
      * @param options options, may be null.
      */
-    private DatePatternConverter(final String[] options) {
+    private DatePatternConverter(final Configuration configuration, final String[] options) {
         super("Date", "date");
-        this.options = options == null ? null : Arrays.copyOf(options, options.length);
-        this.formatter = createFormatter(options);
-        cachedTime = new AtomicReference<>(fromEpochMillis(System.currentTimeMillis()));
-    }
-
-    private CachedTime fromEpochMillis(final long epochMillis) {
-        final MutableInstant temp = new MutableInstant();
-        temp.initFromEpochMilli(epochMillis, 0);
-        return new CachedTime(temp);
+        final String[] safeOptions = options == null ? null : Arrays.copyOf(options, options.length);
+        this.mutableInstantRecycler = configuration.getRecyclerFactory().create(MutableInstant::new);
+        this.formatterRecycler = configuration.getRecyclerFactory().create(() -> createFormatter(safeOptions));
+        final Formatter formatter = formatterRecycler.acquire();
+        try {
+            this.pattern = formatter.toPattern();
+            this.timeZone = formatter.getTimeZone();
+        } finally {
+            formatterRecycler.release(formatter);
+        }
     }
 
     private Formatter createFormatter(final String[] options) {
@@ -221,8 +189,8 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
      * @param options options, may be null.
      * @return instance of pattern converter.
      */
-    public static DatePatternConverter newInstance(final String[] options) {
-        return new DatePatternConverter(options);
+    public static DatePatternConverter newInstance(final Configuration configuration, final String[] options) {
+        return new DatePatternConverter(configuration, options);
     }
 
     private static Formatter createFixedFormatter(final FixedDateFormat fixedDateFormat) {
@@ -275,7 +243,7 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
      * @param date date
      * @param toAppendTo buffer to which formatted date is appended.
      */
-    public void format(final Date date, final StringBuilder toAppendTo) {
+    void format(final Date date, final StringBuilder toAppendTo) {
         format(date.getTime(), toAppendTo);
     }
 
@@ -287,55 +255,23 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
         format(event.getInstant(), output);
     }
 
-    public void format(final long epochMilli, final StringBuilder output) {
-        final MutableInstant instant = getMutableInstant();
-        instant.initFromEpochMilli(epochMilli, 0);
-        format(instant, output);
-    }
-
-    private MutableInstant getMutableInstant() {
-        if (isThreadLocalsEnabled()) {
-            MutableInstant result = threadLocalMutableInstant.get();
-            if (result == null) {
-                result = new MutableInstant();
-                threadLocalMutableInstant.set(result);
-            }
-            return result;
-        }
-        return new MutableInstant();
-    }
-
-    public void format(final Instant instant, final StringBuilder output) {
-        if (isThreadLocalsEnabled()) {
-            formatWithoutAllocation(instant, output);
-        } else {
-            formatWithoutThreadLocals(instant, output);
-        }
-    }
-
-    private void formatWithoutAllocation(final Instant instant, final StringBuilder output) {
-        getThreadLocalFormatter().formatToBuffer(instant, output);
-    }
-
-    private Formatter getThreadLocalFormatter() {
-        Formatter result = threadLocalFormatter.get();
-        if (result == null) {
-            result = createFormatter(options);
-            threadLocalFormatter.set(result);
+    private void format(final long epochMilli, final StringBuilder output) {
+        final MutableInstant instant = mutableInstantRecycler.acquire();
+        try {
+            instant.initFromEpochMilli(epochMilli, 0);
+            format(instant, output);
+        } finally {
+            mutableInstantRecycler.release(instant);
         }
-        return result;
     }
 
-    private void formatWithoutThreadLocals(final Instant instant, final StringBuilder output) {
-        final CachedTime effective;
-        final CachedTime cached = cachedTime.get();
-        if (instant.getEpochSecond() != cached.epochSecond || instant.getNanoOfSecond() != cached.nanoOfSecond) {
-            effective = new CachedTime(instant);
-            cachedTime.compareAndSet(cached, effective);
-        } else {
-            effective = cached;
+    void format(final Instant instant, final StringBuilder output) {
+        final Formatter formatter = formatterRecycler.acquire();
+        try {
+            formatter.formatToBuffer(instant, output);
+        } finally {
+            formatterRecycler.release(formatter);
         }
-        output.append(effective.formatted);
     }
 
     /**
@@ -365,7 +301,7 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
      * @return the pattern string describing this date format.
      */
     public String getPattern() {
-        return formatter.toPattern();
+        return pattern;
     }
 
     /**
@@ -374,6 +310,6 @@ public final class DatePatternConverter extends LogEventPatternConverter impleme
      * @return the timezone used by this date format.
      */
     public TimeZone getTimeZone() {
-        return formatter.getTimeZone();
+        return timeZone;
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
index 4bdda72055..0e2fbd3efd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
@@ -17,6 +17,7 @@
 package org.apache.logging.log4j.core.pattern;
 
 import java.util.TimeZone;
+import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.plugins.Namespace;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -37,8 +38,8 @@ public final class FileDatePatternConverter implements ArrayPatternConverter {
     /**
      * Private constructor.
      */
-    private FileDatePatternConverter(final String... options) {
-        delegate = DatePatternConverter.newInstance(options);
+    private FileDatePatternConverter(final Configuration configuration, final String... options) {
+        delegate = DatePatternConverter.newInstance(configuration, options);
     }
 
     /**
@@ -47,12 +48,12 @@ public final class FileDatePatternConverter implements ArrayPatternConverter {
      * @param options options, may be null.
      * @return instance of pattern converter.
      */
-    public static FileDatePatternConverter newInstance(final String[] options) {
+    public static FileDatePatternConverter newInstance(final Configuration configuration, final String[] options) {
         if (options == null || options.length == 0) {
-            return new FileDatePatternConverter("yyyy-MM-dd");
+            return new FileDatePatternConverter(configuration, "yyyy-MM-dd");
         }
 
-        return new FileDatePatternConverter(options);
+        return new FileDatePatternConverter(configuration, options);
     }
 
     @Override
diff --git a/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/GcFreeLoggingTestUtil.java b/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/GcFreeLoggingTestUtil.java
index 6c248bf6fe..c99c7100f7 100644
--- a/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/GcFreeLoggingTestUtil.java
+++ b/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/GcFreeLoggingTestUtil.java
@@ -17,7 +17,6 @@
 package org.apache.logging.log4j.gctests;
 
 import static java.lang.System.getProperty;
-import static org.apache.logging.log4j.util.Constants.isThreadLocalsEnabled;
 import static org.apache.logging.log4j.util.Constants.isWebApp;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -53,12 +52,10 @@ public enum GcFreeLoggingTestUtil {
 
     public static void executeLogging(final String configurationFile, final Class<?> testClass) throws Exception {
 
-        System.setProperty(LoggingSystemProperty.THREAD_LOCALS_ENABLE.getSystemKey(), "true");
         System.setProperty(Log4jPropertyKey.GC_ENABLE_DIRECT_ENCODERS.getSystemKey(), "true");
         System.setProperty(LoggingSystemProperty.IS_WEBAPP.getSystemKey(), "false");
         System.setProperty(Log4jPropertyKey.CONFIG_LOCATION.getSystemKey(), configurationFile);
 
-        assertTrue(isThreadLocalsEnabled(), "Constants.isThreadLocalsEnabled()");
         assertTrue(Constants.ENABLE_DIRECT_ENCODERS, "Constants.ENABLE_DIRECT_ENCODERS");
         assertFalse(isWebApp(), "Constants.isWebApp()");
 
diff --git a/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/JsonTemplateLayoutGcFreeTest.java b/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/JsonTemplateLayoutGcFreeTest.java
index c7000fbf7c..8e3579db50 100644
--- a/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/JsonTemplateLayoutGcFreeTest.java
+++ b/log4j-gctests/src/test/java/org/apache/logging/log4j/gctests/JsonTemplateLayoutGcFreeTest.java
@@ -33,7 +33,6 @@ public class JsonTemplateLayoutGcFreeTest {
      * This code runs in a separate process, instrumented with the Google Allocation Instrumenter.
      */
     public static void main(final String[] args) throws Exception {
-        System.setProperty("log4j.layout.jsonTemplate.recyclerFactory", "threadLocal");
         System.setProperty(LoggingSystemProperty.THREAD_CONTEXT_GARBAGE_FREE_ENABLED.getSystemKey(), "true");
         GcFreeLoggingTestUtil.executeLogging("gcFreeJsonTemplateLayoutLogging.xml", JsonTemplateLayoutGcFreeTest.class);
     }
diff --git a/log4j-gctests/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml b/log4j-gctests/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
index 498410cd60..6f221e3c9c 100644
--- a/log4j-gctests/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
+++ b/log4j-gctests/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
@@ -24,7 +24,7 @@
           fileName="target/gcFreeJsonTemplateLayoutLogging.log"
           bufferedIO="false"
           append="false">
-      <JsonTemplateLayout recyclerFactory="threadLocal"/>
+      <JsonTemplateLayout/>
     </File>
   </Appenders>
   <Loggers>
diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
index 20f75b4085..fc73019769 100644
--- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
+++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayoutTest.java
@@ -613,8 +613,7 @@ class JsonTemplateLayoutTest {
 
         // Get toByteArrayOutput().
         final byte[] toByteArrayOutputBytes = layout.toByteArray(logEvent);
-        final String toByteArrayOutput =
-                new String(toByteArrayOutputBytes, 0, toByteArrayOutputBytes.length, layout.getCharset());
+        final String toByteArrayOutput = new String(toByteArrayOutputBytes, layout.getCharset());
 
         // Get encode() output.
         final ByteBuffer byteBuffer = ByteBuffer.allocate(512 * 1024);
@@ -1307,16 +1306,6 @@ class JsonTemplateLayoutTest {
         });
     }
 
-    @Test
-    void test_MessageParameterResolver_with_ParameterizedMessageFactory() {
-        testMessageParameterResolver(ParameterizedMessageFactory.INSTANCE);
-    }
-
-    @Test
-    void test_MessageParameterResolver_noParameters_with_ParameterizedMessageFactory() {
-        testMessageParameterResolverNoParameters(ParameterizedMessageFactory.INSTANCE);
-    }
-
     @Test
     void test_MessageParameterResolver_with_ReusableMessageFactory() {
         testMessageParameterResolver(ReusableMessageFactory.INSTANCE);
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncLoggersLocationBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncLoggersLocationBenchmark.java
index 25cf4cb3be..bc965eedae 100644
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncLoggersLocationBenchmark.java
+++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/AsyncLoggersLocationBenchmark.java
@@ -57,7 +57,6 @@ public class AsyncLoggersLocationBenchmark {
         System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
         System.setProperty("AsyncLogger.RingBufferSize", "262144");
         System.setProperty("AsyncLogger.WaitStrategy", "Yield");
-        // System.setProperty("log4j2.enable.threadlocals", "true");
         // System.setProperty("log4j.format.msg.async", "true");
 
         logger = LogManager.getLogger(getClass());
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
index da5271fb32..787516e76d 100644
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
+++ b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
@@ -30,7 +30,6 @@ import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
 import org.apache.logging.log4j.perf.nogc.OpenHashStringMap;
 import org.apache.logging.log4j.spi.CopyOnWriteOpenHashMapThreadContextMap;
-import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.GarbageFreeOpenHashMapThreadContextMap;
 import org.apache.logging.log4j.spi.LoggingSystemProperty;
 import org.apache.logging.log4j.spi.ThreadContextMap;
@@ -71,7 +70,6 @@ import org.openjdk.jmh.annotations.Warmup;
 @Fork(1)
 @State(Scope.Benchmark)
 public class ThreadContextBenchmark {
-    private static final String DEFAULT_CONTEXT_MAP = "Default";
     private static final String COPY_OPENHASH_MAP = "CopyOpenHash";
     private static final String COPY_ARRAY_MAP = "CopySortedArray";
     private static final String NO_GC_OPENHASH_MAP = "NoGcOpenHash";
@@ -79,7 +77,6 @@ public class ThreadContextBenchmark {
     private static final Map<String, Class<? extends ThreadContextMap>> IMPLEMENTATIONS = new HashMap<>();
 
     static {
-        IMPLEMENTATIONS.put(DEFAULT_CONTEXT_MAP, DefaultThreadContextMap.class);
         IMPLEMENTATIONS.put(COPY_OPENHASH_MAP, CopyOnWriteOpenHashMapThreadContextMap.class);
         IMPLEMENTATIONS.put(
                 COPY_ARRAY_MAP,
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java
deleted file mode 100644
index fff26cefd4..0000000000
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.perf.jmh;
-
-import java.util.concurrent.ConcurrentHashMap;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.State;
-
-/**
- * Compares performance of ThreadLocal, vs ConcurrentHashMap&lt;Thread, Object&gt;.
- */
-// ============================== HOW TO RUN THIS TEST: ====================================
-// (Quick build: mvn -DskipTests=true clean package -pl log4j-perf -am )
-//
-// single thread:
-// java -jar log4j-perf/target/benchmarks.jar ".*ThreadLocalVsConcurrentHashMap.*" -f 1 -wi 10 -i 20 -tu ns -bm sample
-//
-// four threads:
-// java -jar log4j-perf/target/benchmarks.jar ".*ThreadLocalVsConcurrentHashMap.*" -f 1 -wi 10 -i 20 -tu ns -bm sample
-// -t 4
-//
-// Usage help:
-// java -jar log4j-perf/target/benchmarks.jar -help
-//
-@State(Scope.Benchmark)
-public class ThreadLocalVsConcurrentHashMapBenchmark {
-
-    private static final String VALUE = "value";
-    private static ConcurrentHashMap<Thread, StringBuilder> map = new ConcurrentHashMap<>();
-    private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
-
-    @Benchmark
-    public String newInstance() {
-        final StringBuilder sb = getNew();
-        sb.append(VALUE);
-        return sb.toString();
-    }
-
-    @Benchmark
-    public String threadLocal() {
-        final StringBuilder sb = getThreadLocal();
-        sb.append(VALUE);
-        return sb.toString();
-    }
-
-    @Benchmark
-    public String concurrentHashMap() {
-        final StringBuilder sb = getConcurrentMap();
-        sb.append(VALUE);
-        return sb.toString();
-    }
-
-    private StringBuilder getNew() {
-        final StringBuilder buf = new StringBuilder();
-        return buf;
-    }
-
-    private StringBuilder getThreadLocal() {
-        StringBuilder buf = threadLocal.get();
-        if (buf == null) {
-            buf = new StringBuilder();
-            threadLocal.set(buf);
-        }
-        buf.setLength(0);
-        return buf;
-    }
-
-    private StringBuilder getConcurrentMap() {
-        StringBuilder buf = map.get(Thread.currentThread());
-        if (buf == null) {
-            buf = new StringBuilder();
-            map.put(Thread.currentThread(), buf);
-        }
-        buf.setLength(0);
-        return buf;
-    }
-}
diff --git a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java b/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java
deleted file mode 100644
index 8623eed1b9..0000000000
--- a/log4j-perf-test/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to you under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.logging.log4j.perf.jmh;
-
-import java.util.List;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.DefaultConfiguration;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
-import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
-import org.apache.logging.log4j.core.pattern.PatternFormatter;
-import org.apache.logging.log4j.core.pattern.PatternParser;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.SimpleMessage;
-import org.jctools.queues.MpmcArrayQueue;
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Scope;
-import org.openjdk.jmh.annotations.State;
-
-/**
- * Checks {@link PatternFormatter} performance with various StringBuilder
- * caching strategies: no-op, ThreadLocal, and JCTools MPMC queue.
- */
-// ============================== HOW TO RUN THIS TEST: ====================================
-// (Quick build: mvn -DskipTests=true clean package -pl log4j-perf -am )
-//
-// single thread:
-// java -jar log4j-perf/target/benchmarks.jar ".*ThreadLocalVsPool.*" -f 1 -wi 10 -i 20 -tu ns -bm sample
-//
-// four threads:
-// java -jar log4j-perf/target/benchmarks.jar ".*ThreadLocalVsPool.*" -f 1 -wi 10 -i 20 -tu ns -bm sample -t 4
-//
-// Usage help:
-// java -jar log4j-perf/target/benchmarks.jar -help
-//
-@State(Scope.Benchmark)
-public class ThreadLocalVsPoolBenchmark {
-
-    private static final LogEvent LOG_EVENT = createLogEvent();
-
-    private static final List<PatternFormatter> FORMATTERS = createFormatters();
-
-    private static LogEvent createLogEvent() {
-        final String loggerName = "name(ignored)";
-        final String loggerFqcn = "com.mycom.myproject.mypackage.MyClass";
-        final Level level = Level.DEBUG;
-        final String messageString = "AB!(%087936DZYXQWEIOP$#^~-=/><nb"; // length=32
-        final Message message = new SimpleMessage(messageString);
-        final long timestamp = 12345678;
-        return Log4jLogEvent.newBuilder()
-                .setLoggerName(loggerName)
-                .setLoggerFqcn(loggerFqcn)
-                .setLevel(level)
-                .setMessage(message)
-                .setTimeMillis(timestamp)
-                .build();
-    }
-
-    private static List<PatternFormatter> createFormatters() {
-        final Configuration config = new DefaultConfiguration();
-        final PatternParser parser = new PatternParser(config, "Converter", LogEventPatternConverter.class);
-        return parser.parse("%d %5p [%t] %c{1} %X{transactionId} - %m%n", false, true);
-    }
-
-    private abstract static class StringBuilderPool {
-
-        abstract StringBuilder acquire();
-
-        abstract void release(StringBuilder stringBuilder);
-
-        StringBuilder createStringBuilder() {
-            return new StringBuilder(1024 * 32);
-        }
-    }
-
-    private static final class AllocatePool extends StringBuilderPool {
-
-        private static final AllocatePool INSTANCE = new AllocatePool();
-
-        @Override
-        public StringBuilder acquire() {
-            return createStringBuilder();
-        }
-
-        @Override
-        public void release(final StringBuilder stringBuilder) {}
-    }
-
-    private static final class ThreadLocalPool extends StringBuilderPool {
-
-        private static final ThreadLocalPool INSTANCE = new ThreadLocalPool();
-
-        private final ThreadLocal<StringBuilder> stringBuilderRef = ThreadLocal.withInitial(this::createStringBuilder);
-
-        @Override
-        public StringBuilder acquire() {
-            return stringBuilderRef.get();
-        }
-
-        @Override
-        public void release(final StringBuilder stringBuilder) {
-            stringBuilder.setLength(0);
-        }
-    }
-
-    private static final class JcPool extends StringBuilderPool {
-
-        private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
-
-        private static final int MPMC_REQUIRED_MIN_CAPACITY = 2;
-
-        // Putting the under-provisioned instance to a wrapper class to prevent
-        // the initialization of JcPool itself when there are insufficient CPU
-        // cores.
-        private enum UnderProvisionedInstanceHolder {
-            ;
-
-            private static final JcPool INSTANCE = createInstance();
-
-            private static JcPool createInstance() {
-                if (CPU_COUNT <= MPMC_REQUIRED_MIN_CAPACITY) {
-                    throw new IllegalArgumentException("insufficient CPU cores");
-                }
-                return new JcPool(MPMC_REQUIRED_MIN_CAPACITY);
-            }
-        }
-
-        private static final JcPool RIGHT_PROVISIONED_INSTANCE =
-                new JcPool(Math.max(MPMC_REQUIRED_MIN_CAPACITY, CPU_COUNT));
-
-        private final MpmcArrayQueue<StringBuilder> stringBuilders;
-
-        private JcPool(final int capacity) {
-            this.stringBuilders = new MpmcArrayQueue<>(capacity);
-        }
-
-        @Override
-        public StringBuilder acquire() {
-            final StringBuilder stringBuilder = stringBuilders.poll();
-            return stringBuilder != null ? stringBuilder : createStringBuilder();
-        }
-
-        @Override
-        public void release(final StringBuilder stringBuilder) {
-            stringBuilder.setLength(0);
-            stringBuilders.offer(stringBuilder);
-        }
-    }
-
-    @Benchmark
-    public int allocate() {
-        return findSerializedLength(AllocatePool.INSTANCE);
-    }
-
-    @Benchmark
-    public int threadLocal() {
-        return findSerializedLength(ThreadLocalPool.INSTANCE);
-    }
-
-    @Benchmark
-    public int rightProvedJc() {
-        return findSerializedLength(JcPool.RIGHT_PROVISIONED_INSTANCE);
-    }
-
-    @Benchmark
-    public int underProvedJc() {
-        return findSerializedLength(JcPool.UnderProvisionedInstanceHolder.INSTANCE);
-    }
-
-    private int findSerializedLength(final StringBuilderPool pool) {
-        final StringBuilder stringBuilder = pool.acquire();
-        serialize(stringBuilder);
-        final int length = stringBuilder.length();
-        pool.release(stringBuilder);
-        return length;
-    }
-
-    private void serialize(final StringBuilder stringBuilder) {
-        // noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
-        for (int formatterIndex = 0; formatterIndex < FORMATTERS.size(); formatterIndex++) {
-            final PatternFormatter formatter = FORMATTERS.get(formatterIndex);
-            formatter.format(LOG_EVENT, stringBuilder);
-        }
-    }
-}
diff --git a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
index 47d760aa7b..7be02d63cb 100644
--- a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
+++ b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
@@ -30,7 +30,7 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.ParameterizedMessageFactory;
+import org.apache.logging.log4j.message.ReusableMessageFactory;
 import org.apache.logging.log4j.message.StringFormatterMessageFactory;
 import org.apache.logging.log4j.spi.LoggingSystem;
 import org.apache.logging.log4j.test.junit.UsingStatusListener;
@@ -102,7 +102,7 @@ public class LoggerTest {
         final Logger testLogger = testMessageFactoryMismatch(
                 "getLogger_String_MessageFactoryMismatch",
                 StringFormatterMessageFactory.INSTANCE,
-                ParameterizedMessageFactory.INSTANCE);
+                new ReusableMessageFactory());
         testLogger.debug("%,d", Integer.MAX_VALUE);
         assertThat(list.strList, hasSize(1));
         assertThat(list.strList, hasItem(String.format("%,d", Integer.MAX_VALUE)));
diff --git a/src/changelog/.3.x.x/remove_thread_locals_property.xml b/src/changelog/.3.x.x/remove_thread_locals_property.xml
new file mode 100644
index 0000000000..deb4701d2a
--- /dev/null
+++ b/src/changelog/.3.x.x/remove_thread_locals_property.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns="http://logging.apache.org/log4j/changelog"
+       xsi:schemaLocation="http://logging.apache.org/log4j/changelog https://logging.apache.org/log4j/changelog-0.1.3.xsd"
+       type="removed">
+  <issue id="2105" link="https://github.com/apache/logging-log4j2/issues/2105"/>
+  <description format="asciidoc">Remove `log4j2.enable.threadlocals` property</description>
+</entry>
diff --git a/src/site/_release-notes/_3.x.x.adoc b/src/site/_release-notes/_3.x.x.adoc
index 6f01ff61f1..138505b155 100644
--- a/src/site/_release-notes/_3.x.x.adoc
+++ b/src/site/_release-notes/_3.x.x.adoc
@@ -24,10 +24,12 @@ This release contains...
 === Added
 
 * Add and update DSLs for setting up dependency injection for test and non-test code. (https://github.com/apache/logging-log4j2/issues/2147[2147])
+* Add a new properties configuration factory based on `jackson-dataformat-properties`.
 
 [#release-notes-3-x-x-changed]
 === Changed
 
+* Moves legacy `2.x` properties configuration factory to `log4j-config-properties-legacy`.
 * Split off YAML configuration into a new `log4j-config-yaml` module.
 * Update `com.google.code.java-allocation-instrumenter:java-allocation-instrumenter` to version `3.3.4` (https://github.com/apache/logging-log4j2/pull/2102[2102])
 * Update `com.google.guava:guava` to version `33.0.0-jre` (https://github.com/apache/logging-log4j2/pull/2108[2108])
@@ -46,6 +48,7 @@ This release contains...
 === Removed
 
 * Remove `org.apache.logging.log4j.core.parser` and related packages. (https://github.com/apache/logging-log4j2/pull/2154[2154])
+* Remove `log4j2.enable.threadlocals` property (https://github.com/apache/logging-log4j2/issues/2105[2105])
 
 [#release-notes-3-x-x-updated]
 === Updated
diff --git a/src/site/asciidoc/manual/extending.adoc b/src/site/asciidoc/manual/extending.adoc
index b14f319fb4..f00302d16b 100644
--- a/src/site/asciidoc/manual/extending.adoc
+++ b/src/site/asciidoc/manual/extending.adoc
@@ -217,8 +217,7 @@ the `LogEventFactory` is not used.
 == MessageFactory
 
 A `MessageFactory` is used to generate `Message` objects. Applications may
-replace the standard `ParameterizedMessageFactory` (or
-`ReusableMessageFactory` in garbage-free mode) by setting the value of the
+replace the standard `ReusableMessageFactory` by setting the value of the
 system property `log4j2.messageFactory` to the name of the custom
 `MessageFactory` class.
 
diff --git a/src/site/asciidoc/manual/systemproperties.adoc b/src/site/asciidoc/manual/systemproperties.adoc
index f743494c3f..a1f7fd576f 100644
--- a/src/site/asciidoc/manual/systemproperties.adoc
+++ b/src/site/asciidoc/manual/systemproperties.adoc
@@ -183,9 +183,7 @@ classes with the current thread's context class loader before falling back to th
 |factory
 |log4j2.messageFactory, LOG4J_MESSAGE_FACTORY
 |System/Application
-|org.apache.logging.log4j.message. ParameterizedMessageFactory or
-org.apache.logging.log4j.message. ReusableMessageFactory in garbage-free
-mode
+`org.apache.logging.log4j.message.ReusableMessageFactory`
 |Default message factory used by Loggers if no factory was specified.
 
 |Message