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

pkarwasz pushed a commit to branch feature/provider-revamp
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 3577d1d365e9c0fd9b5ebe13d0142db7bc2fabb4
Author: Piotr P. Karwasz <piotr.git...@karwasz.org>
AuthorDate: Wed Mar 13 10:27:45 2024 +0100

    Centralize initialization in `Provider` class
    
    We move the code responsible for the instantiation of
    `LoggerContextFactory` and `ThreadContextMap` from the static entry
    points to the logging system (`LogManager` and `ThreadContext`) to the
    `Provider` class.
    
    The `Provider` class is instantiated using `ServiceLoader`, so
    `log4j-core` 2.x and 3.x can reimplement the initialization process
    according to their own rules. E.g. `log4j-core` 3.x can use the DI to
    create an instance of `LoggerContextFactory` and `ThreadContextMap`.
    
    The following modification were performed:
    
    * a **new** system property `log4j.provider` was introduced,
    * the old `log4j2.loggerContextFactory` has been deprecated and revised: if 
set it
      selects the first provider that uses the given `LoggerContextFactory`.
      Therefore it selects now both the context factory and thread context
      map implementations,
    * private static configuration values were removed from
      `ThreadContextMap` implementations, helping test parallelisation,
    * a distinct `NoOpThreadContextStack` implementation has been
      introduced.
---
 .../org/apache/logging/log4j/TestProvider.java     |   2 +-
 .../log4j/spi/DefaultThreadContextMapTest.java     |  25 --
 .../logging/log4j/spi/ThreadContextMapTest.java    |  76 +++++
 .../logging/log4j/util/ProviderUtilTest.java       |  71 ++--
 .../services/org.apache.logging.log4j.spi.Provider |   3 +-
 .../java/org/apache/logging/log4j/LogManager.java  |  69 +---
 .../org/apache/logging/log4j/ThreadContext.java    |  90 +++--
 .../CopyOnWriteSortedArrayThreadContextMap.java    |  46 +--
 .../logging/log4j/spi/DefaultThreadContextMap.java |  46 +--
 .../log4j/spi/DefaultThreadContextStack.java       |   8 +
 .../GarbageFreeSortedArrayThreadContextMap.java    |  41 +--
 .../org/apache/logging/log4j/spi/Provider.java     | 365 ++++++++++++++++++---
 .../logging/log4j/spi/ThreadContextMapFactory.java |  93 +-----
 .../apache/logging/log4j/util/ProviderUtil.java    | 169 ++++++++--
 .../apache/logging/log4j/util/package-info.java    |   2 +-
 .../logging/log4j/core/impl/Log4jProvider.java     |   2 +-
 16 files changed, 676 insertions(+), 432 deletions(-)

diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java
index 3296647a6c..5feb4a1f25 100644
--- a/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java
+++ b/log4j-api-test/src/test/java/org/apache/logging/log4j/TestProvider.java
@@ -24,6 +24,6 @@ import org.apache.logging.log4j.test.TestLoggerContextFactory;
  */
 public class TestProvider extends Provider {
     public TestProvider() {
-        super(0, "2.6.0", TestLoggerContextFactory.class);
+        super(10, "2.6.0", TestLoggerContextFactory.class);
     }
 }
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
index a0ca976d73..dedc5be86b 100644
--- 
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
@@ -24,11 +24,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.logging.log4j.test.junit.InitializesThreadContext;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
 import org.apache.logging.log4j.test.junit.UsingThreadContextMap;
 import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.ClearSystemProperty;
 
 /**
  * Tests the {@code DefaultThreadContextMap} class.
@@ -217,26 +214,4 @@ public class DefaultThreadContextMapTest {
         map.put("key2", "value2");
         assertEquals("{key2=value2}", map.toString());
     }
-
-    @Test
-    @ClearSystemProperty(key = DefaultThreadContextMap.INHERITABLE_MAP)
-    @InitializesThreadContext
-    public void testThreadLocalNotInheritableByDefault() {
-        ThreadContextMapFactory.init();
-        final ThreadLocal<Map<String, String>> threadLocal = 
DefaultThreadContextMap.createThreadLocalMap(true);
-        assertFalse(threadLocal instanceof InheritableThreadLocal<?>);
-    }
-
-    @Test
-    @SetTestProperty(key = DefaultThreadContextMap.INHERITABLE_MAP, value = 
"true")
-    @InitializesThreadContext
-    public void testThreadLocalInheritableIfConfigured() {
-        ThreadContextMapFactory.init();
-        try {
-            final ThreadLocal<Map<String, String>> threadLocal = 
DefaultThreadContextMap.createThreadLocalMap(true);
-            assertTrue(threadLocal instanceof InheritableThreadLocal<?>);
-        } finally {
-            System.clearProperty(DefaultThreadContextMap.INHERITABLE_MAP);
-        }
-    }
 }
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java
new file mode 100644
index 0000000000..42df896f9e
--- /dev/null
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/spi/ThreadContextMapTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.assertj.core.api.Assertions.assertThat;
+
+import java.time.Duration;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Stream;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class ThreadContextMapTest {
+
+    static Stream<ThreadContextMap> defaultMaps() {
+        return Stream.of(
+                new DefaultThreadContextMap(),
+                new CopyOnWriteSortedArrayThreadContextMap(),
+                new GarbageFreeSortedArrayThreadContextMap());
+    }
+
+    static Stream<ThreadContextMap> inheritableMaps() {
+        final Properties props = new Properties();
+        props.setProperty("log4j2.isThreadContextMapInheritable", "true");
+        final PropertiesUtil util = new PropertiesUtil(props);
+        return Stream.of(
+                new DefaultThreadContextMap(true, util),
+                new CopyOnWriteSortedArrayThreadContextMap(util),
+                new GarbageFreeSortedArrayThreadContextMap(util));
+    }
+
+    @ParameterizedTest
+    @MethodSource("defaultMaps")
+    void threadLocalNotInheritableByDefault(final ThreadContextMap contextMap) 
{
+        contextMap.put("key", "threadLocalNotInheritableByDefault");
+        final ExecutorService executorService = 
Executors.newSingleThreadExecutor();
+        try {
+            assertThat(executorService.submit(() -> contextMap.get("key")))
+                    .succeedsWithin(Duration.ofSeconds(1))
+                    .isEqualTo(null);
+        } finally {
+            executorService.shutdown();
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("inheritableMaps")
+    void threadLocalInheritableIfConfigured(final ThreadContextMap contextMap) 
{
+        contextMap.put("key", "threadLocalInheritableIfConfigured");
+        final ExecutorService executorService = 
Executors.newSingleThreadExecutor();
+        try {
+            assertThat(executorService.submit(() -> contextMap.get("key")))
+                    .succeedsWithin(Duration.ofSeconds(1))
+                    .isEqualTo("threadLocalInheritableIfConfigured");
+        } finally {
+            executorService.shutdown();
+        }
+    }
+}
diff --git 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java
 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java
index fbba1ccacc..c9b59eb813 100644
--- 
a/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java
+++ 
b/log4j-api-test/src/test/java/org/apache/logging/log4j/util/ProviderUtilTest.java
@@ -16,36 +16,61 @@
  */
 package org.apache.logging.log4j.util;
 
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.spi.LoggerContext;
-import org.apache.logging.log4j.test.TestLoggerContext;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Properties;
+import org.apache.logging.log4j.TestProvider;
+import org.apache.logging.log4j.test.TestLoggerContextFactory;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+
+@Execution(ExecutionMode.CONCURRENT)
+class ProviderUtilTest {
 
-public class ProviderUtilTest {
+    /*
+     * Force initialization of ProviderUtil#PROVIDERS
+     */
+    static {
+        ProviderUtil.lazyInit();
+    }
+
+    @Test
+    void should_select_provider_with_highest_priority() {
+        final PropertiesUtil properties = new PropertiesUtil(new Properties());
+        assertThat(ProviderUtil.selectProvider(properties))
+                .as("check selected provider")
+                .isInstanceOf(TestProvider.class);
+    }
+
+    @Test
+    void should_recognize_log4j_provider_property() {
+        final Properties map = new Properties();
+        map.setProperty("log4j.provider", LocalProvider.class.getName());
+        final PropertiesUtil properties = new PropertiesUtil(map);
+        assertThat(ProviderUtil.selectProvider(properties))
+                .as("check selected provider")
+                .isInstanceOf(LocalProvider.class);
+    }
 
     @Test
-    public void complexTest() throws Exception {
-        final File file = new File("target/classes");
-        final ClassLoader classLoader =
-                new URLClassLoader(new URL[] {file.toURI().toURL()});
-        final Worker worker = new Worker();
-        worker.setContextClassLoader(classLoader);
-        worker.start();
-        worker.join();
-        assertTrue(worker.context instanceof TestLoggerContext, "Incorrect 
LoggerContext");
+    void should_recognize_log4j_factory_property() {
+        final Properties map = new Properties();
+        map.setProperty("log4j2.loggerContextFactory", 
LocalLoggerContextFactory.class.getName());
+        final PropertiesUtil properties = new PropertiesUtil(map);
+        
assertThat(ProviderUtil.selectProvider(properties).getLoggerContextFactory())
+                .as("check selected logger context factory")
+                .isInstanceOf(LocalLoggerContextFactory.class);
     }
 
-    private static class Worker extends Thread {
-        LoggerContext context = null;
+    public static class LocalLoggerContextFactory extends 
TestLoggerContextFactory {}
 
-        @Override
-        public void run() {
-            context = LogManager.getContext(false);
+    /**
+     * A provider with a smaller priority than {@link 
org.apache.logging.log4j.TestProvider}.
+     */
+    public static class LocalProvider extends 
org.apache.logging.log4j.spi.Provider {
+        public LocalProvider() {
+            super(0, CURRENT_VERSION, LocalLoggerContextFactory.class);
         }
     }
 }
diff --git 
a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
 
b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
index 5ae649a3f9..280fd54e8c 100644
--- 
a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
+++ 
b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -1 +1,2 @@
-org.apache.logging.log4j.TestProvider
\ No newline at end of file
+org.apache.logging.log4j.TestProvider
+org.apache.logging.log4j.util.ProviderUtilTest.LocalProvider
\ No newline at end of file
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
index 02e03235d9..d7def7ac7e 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
@@ -17,20 +17,14 @@
 package org.apache.logging.log4j;
 
 import java.net.URI;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
 import org.apache.logging.log4j.internal.LogManagerStatus;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.StringFormatterMessageFactory;
 import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
 import org.apache.logging.log4j.spi.LoggerContext;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.spi.Terminable;
 import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.ProviderUtil;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.Strings;
@@ -48,8 +42,10 @@ public class LogManager {
 
     /**
      * Log4j property to set to the fully qualified class name of a custom 
implementation of
-     * {@link org.apache.logging.log4j.spi.LoggerContextFactory}.
+     * {@link LoggerContextFactory}.
+     * @deprecated Replaced since 2.24.0 with {@value 
org.apache.logging.log4j.spi.Provider#PROVIDER_PROPERTY_NAME}.
      */
+    @Deprecated
     public static final String FACTORY_PROPERTY_NAME = 
"log4j2.loggerContextFactory";
 
     /**
@@ -62,69 +58,14 @@ public class LogManager {
     // for convenience
     private static final String FQCN = LogManager.class.getName();
 
-    private static volatile LoggerContextFactory factory;
+    private static volatile LoggerContextFactory factory =
+            ProviderUtil.getProvider().getLoggerContextFactory();
 
     /*
      * Scans the classpath to find all logging implementation. Currently, only 
one will be used but this could be
      * extended to allow multiple implementations to be used.
      */
     static {
-        // Shortcut binding to force a specific logging implementation.
-        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
-        final String factoryClassName = 
managerProps.getStringProperty(FACTORY_PROPERTY_NAME);
-        if (factoryClassName != null) {
-            try {
-                factory = LoaderUtil.newCheckedInstanceOf(factoryClassName, 
LoggerContextFactory.class);
-            } catch (final ClassNotFoundException cnfe) {
-                LOGGER.error("Unable to locate configured LoggerContextFactory 
{}", factoryClassName);
-            } catch (final Exception ex) {
-                LOGGER.error("Unable to create configured LoggerContextFactory 
{}", factoryClassName, ex);
-            }
-        }
-
-        if (factory == null) {
-            final SortedMap<Integer, LoggerContextFactory> factories = new 
TreeMap<>();
-            // note that the following initial call to ProviderUtil may block 
until a Provider has been installed when
-            // running in an OSGi environment
-            if (ProviderUtil.hasProviders()) {
-                for (final Provider provider : ProviderUtil.getProviders()) {
-                    final Class<? extends LoggerContextFactory> factoryClass = 
provider.loadLoggerContextFactory();
-                    if (factoryClass != null) {
-                        try {
-                            factories.put(provider.getPriority(), 
LoaderUtil.newInstanceOf(factoryClass));
-                        } catch (final Exception e) {
-                            LOGGER.error(
-                                    "Unable to create class {} specified in 
provider URL {}",
-                                    factoryClass.getName(),
-                                    provider.getUrl(),
-                                    e);
-                        }
-                    }
-                }
-
-                if (factories.isEmpty()) {
-                    LOGGER.error(
-                            "Log4j2 could not find a logging implementation. 
Please add log4j-core to the classpath. Using SimpleLogger to log to the 
console...");
-                    factory = SimpleLoggerContextFactory.INSTANCE;
-                } else if (factories.size() == 1) {
-                    factory = factories.get(factories.lastKey());
-                } else {
-                    final StringBuilder sb = new StringBuilder("Multiple 
logging implementations found: \n");
-                    for (final Map.Entry<Integer, LoggerContextFactory> entry 
: factories.entrySet()) {
-                        sb.append("Factory: ")
-                                .append(entry.getValue().getClass().getName());
-                        sb.append(", Weighting: 
").append(entry.getKey()).append('\n');
-                    }
-                    factory = factories.get(factories.lastKey());
-                    sb.append("Using factory: 
").append(factory.getClass().getName());
-                    LOGGER.warn(sb.toString());
-                }
-            } else {
-                LOGGER.error(
-                        "Log4j2 could not find a logging implementation. 
Please add log4j-core to the classpath. Using SimpleLogger to log to the 
console...");
-                factory = SimpleLoggerContextFactory.INSTANCE;
-            }
-        }
         LogManagerStatus.setInitialized(true);
     }
 
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 02806b59d5..ddc36de030 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
@@ -23,19 +23,19 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
 import org.apache.logging.log4j.internal.map.StringArrayThreadContextMap;
 import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.apache.logging.log4j.spi.CleanableThreadContextMap;
 import org.apache.logging.log4j.spi.DefaultThreadContextMap;
 import org.apache.logging.log4j.spi.DefaultThreadContextStack;
-import org.apache.logging.log4j.spi.NoOpThreadContextMap;
+import org.apache.logging.log4j.spi.MutableThreadContextStack;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
 import org.apache.logging.log4j.spi.ThreadContextMap;
 import org.apache.logging.log4j.spi.ThreadContextMap2;
 import org.apache.logging.log4j.spi.ThreadContextMapFactory;
 import org.apache.logging.log4j.spi.ThreadContextStack;
 import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.ProviderUtil;
 
 /**
  * The ThreadContext allows applications to store information either in a Map 
or a Stack.
@@ -55,8 +55,6 @@ public final class ThreadContext {
 
         private static final long serialVersionUID = 1L;
 
-        private static final Iterator<String> EMPTY_ITERATOR = new 
EmptyIterator<>();
-
         @Override
         public String pop() {
             return null;
@@ -101,42 +99,45 @@ public final class ThreadContext {
 
         @Override
         public ContextStack copy() {
-            return this;
+            return new MutableThreadContextStack();
         }
 
         @Override
-        public <T> T[] toArray(final T[] a) {
+        public <T> T[] toArray(final T[] ignored) {
             throw new UnsupportedOperationException();
         }
 
         @Override
-        public boolean add(final String e) {
+        public boolean add(final String ignored) {
             throw new UnsupportedOperationException();
         }
 
         @Override
-        public boolean containsAll(final Collection<?> c) {
+        public void clear() {}
+
+        @Override
+        public boolean containsAll(final Collection<?> ignored) {
             return false;
         }
 
         @Override
-        public boolean addAll(final Collection<? extends String> c) {
+        public boolean addAll(final Collection<? extends String> ignored) {
             throw new UnsupportedOperationException();
         }
 
         @Override
-        public boolean removeAll(final Collection<?> c) {
+        public boolean removeAll(final Collection<?> ignored) {
             throw new UnsupportedOperationException();
         }
 
         @Override
-        public boolean retainAll(final Collection<?> c) {
+        public boolean retainAll(final Collection<?> ignored) {
             throw new UnsupportedOperationException();
         }
 
         @Override
         public Iterator<String> iterator() {
-            return EMPTY_ITERATOR;
+            return Collections.emptyIterator();
         }
 
         @Override
@@ -150,26 +151,34 @@ public final class ThreadContext {
         }
     }
 
-    /**
-     * An empty iterator. Since Java 1.7 added the Collections.emptyIterator() 
method, we have to make do.
-     *
-     * @param <E> the type of the empty iterator
-     */
-    private static class EmptyIterator<E> implements Iterator<E> {
+    private static final class NoOpThreadContextStack extends 
EmptyThreadContextStack {
+
+        @Override
+        public boolean add(final String ignored) {
+            return false;
+        }
+
+        @Override
+        public boolean addAll(final Collection<? extends String> ignored) {
+            return false;
+        }
+
+        @Override
+        public void push(final String ignored) {}
 
         @Override
-        public boolean hasNext() {
+        public boolean remove(final Object ignored) {
             return false;
         }
 
         @Override
-        public E next() {
-            throw new NoSuchElementException("This is an empty iterator!");
+        public boolean removeAll(final Collection<?> ignored) {
+            return false;
         }
 
         @Override
-        public void remove() {
-            // no-op
+        public boolean retainAll(final Collection<?> ignored) {
+            return false;
         }
     }
 
@@ -188,13 +197,12 @@ public final class ThreadContext {
     @SuppressWarnings("PublicStaticCollectionField")
     public static final ThreadContextStack EMPTY_STACK = new 
EmptyThreadContextStack();
 
-    private static final String DISABLE_MAP = "disableThreadContextMap";
     private static final String DISABLE_STACK = "disableThreadContextStack";
     private static final String DISABLE_ALL = "disableThreadContext";
 
-    private static boolean useStack;
-    private static ThreadContextMap contextMap;
     private static ThreadContextStack contextStack;
+
+    private static ThreadContextMap contextMap;
     private static ReadOnlyThreadContextMap readOnlyContextMap;
 
     static {
@@ -209,24 +217,16 @@ public final class ThreadContext {
      * <em>Consider private, used for testing.</em>
      */
     public static void init() {
+        final PropertiesUtil properties = PropertiesUtil.getProperties();
+        contextStack = properties.getBooleanProperty(DISABLE_STACK) || 
properties.getBooleanProperty(DISABLE_ALL)
+                ? new NoOpThreadContextStack()
+                : new DefaultThreadContextStack();
+        // TODO: Fix the tests that need to reset the thread context map to 
use separate instance of the
+        //       provider instead.
         ThreadContextMapFactory.init();
-        contextMap = null;
-        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
-        final boolean disableAll = 
managerProps.getBooleanProperty(DISABLE_ALL);
-        useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || 
disableAll);
-        final boolean useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) 
|| disableAll);
-
-        contextStack = new DefaultThreadContextStack(useStack);
-        if (!useMap) {
-            contextMap = new NoOpThreadContextMap();
-        } else {
-            contextMap = ThreadContextMapFactory.createThreadContextMap();
-        }
-        if (contextMap instanceof ReadOnlyThreadContextMap) {
-            readOnlyContextMap = (ReadOnlyThreadContextMap) contextMap;
-        } else {
-            readOnlyContextMap = null;
-        }
+        contextMap = ProviderUtil.getProvider().getThreadContextMapInstance();
+        readOnlyContextMap =
+                contextMap instanceof ReadOnlyThreadContextMap ? 
(ReadOnlyThreadContextMap) contextMap : null;
     }
 
     /**
@@ -385,8 +385,6 @@ public final class ThreadContext {
      * @return the internal data structure used to store thread context 
key-value pairs or {@code null}
      * @see ThreadContextMapFactory
      * @see DefaultThreadContextMap
-     * @see org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap
-     * @see org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap
      * @since 2.8
      */
     public static ReadOnlyThreadContextMap getThreadContextMap() {
@@ -434,7 +432,7 @@ public final class ThreadContext {
      * @param stack The stack to use.
      */
     public static void setStack(final Collection<String> stack) {
-        if (stack.isEmpty() || !useStack) {
+        if (stack.isEmpty()) {
             return;
         }
         contextStack.clear();
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
index 490507c8fc..ca9f501c21 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/CopyOnWriteSortedArrayThreadContextMap.java
@@ -54,48 +54,32 @@ class CopyOnWriteSortedArrayThreadContextMap implements 
ReadOnlyThreadContextMap
 
     private static final StringMap EMPTY_CONTEXT_DATA = new 
SortedArrayStringMap(1);
 
-    private static volatile int initialCapacity;
-    private static volatile boolean inheritableMap;
-
-    /**
-     * Initializes static variables based on system properties. Normally 
called when this class is initialized by the VM
-     * and when Log4j is reconfigured.
-     */
-    static void init() {
-        final PropertiesUtil properties = PropertiesUtil.getProperties();
-        initialCapacity = 
properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, 
DEFAULT_INITIAL_CAPACITY);
-        inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP);
-    }
-
     static {
         EMPTY_CONTEXT_DATA.freeze();
-        init();
     }
 
+    private final int initialCapacity;
     private final ThreadLocal<StringMap> localMap;
 
     public CopyOnWriteSortedArrayThreadContextMap() {
-        this.localMap = createThreadLocalMap();
+        this(PropertiesUtil.getProperties());
     }
 
-    // LOG4J2-479: by default, use a plain ThreadLocal, only use 
InheritableThreadLocal if configured.
-    // (This method is package protected for JUnit tests.)
-    private ThreadLocal<StringMap> createThreadLocalMap() {
-        if (inheritableMap) {
-            return new InheritableThreadLocal<StringMap>() {
-                @Override
-                protected StringMap childValue(final StringMap parentValue) {
-                    if (parentValue == null) {
-                        return null;
+    CopyOnWriteSortedArrayThreadContextMap(final PropertiesUtil properties) {
+        initialCapacity = 
properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, 
DEFAULT_INITIAL_CAPACITY);
+        localMap = properties.getBooleanProperty(INHERITABLE_MAP)
+                ? new InheritableThreadLocal<StringMap>() {
+                    @Override
+                    protected StringMap childValue(final StringMap 
parentValue) {
+                        if (parentValue == null) {
+                            return null;
+                        }
+                        final StringMap stringMap = 
createStringMap(parentValue);
+                        stringMap.freeze();
+                        return stringMap;
                     }
-                    final StringMap stringMap = createStringMap(parentValue);
-                    stringMap.freeze();
-                    return stringMap;
                 }
-            };
-        }
-        // if not inheritable, return plain ThreadLocal with null as initial 
value
-        return new ThreadLocal<>();
+                : new ThreadLocal<StringMap>();
     }
 
     /**
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
index 3c45eee98f..b4e0a48bef 100644
--- 
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
@@ -43,40 +43,30 @@ public class DefaultThreadContextMap implements 
ThreadContextMap, ReadOnlyString
     private final boolean useMap;
     private final ThreadLocal<Map<String, String>> localMap;
 
-    private static boolean inheritableMap;
-
-    static {
-        init();
-    }
-
-    // LOG4J2-479: by default, use a plain ThreadLocal, only use 
InheritableThreadLocal if configured.
-    // (This method is package protected for JUnit tests.)
-    static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean 
isMapEnabled) {
-        if (inheritableMap) {
-            return new InheritableThreadLocal<Map<String, String>>() {
-                @Override
-                protected Map<String, String> childValue(final Map<String, 
String> parentValue) {
-                    return parentValue != null && isMapEnabled //
-                            ? Collections.unmodifiableMap(new 
HashMap<>(parentValue)) //
-                            : null;
-                }
-            };
-        }
-        // if not inheritable, return plain ThreadLocal with null as initial 
value
-        return new ThreadLocal<>();
-    }
-
-    static void init() {
-        inheritableMap = 
PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP);
-    }
-
     public DefaultThreadContextMap() {
         this(true);
     }
 
+    /**
+     * @deprecated Since 2.24.0 use the default constructor or {@link 
NoOpThreadContextMap} instead.
+     */
+    @Deprecated
     public DefaultThreadContextMap(final boolean useMap) {
+        this(useMap, PropertiesUtil.getProperties());
+    }
+
+    DefaultThreadContextMap(final boolean useMap, final PropertiesUtil 
properties) {
         this.useMap = useMap;
-        this.localMap = createThreadLocalMap(useMap);
+        localMap = properties.getBooleanProperty(INHERITABLE_MAP)
+                ? new InheritableThreadLocal<Map<String, String>>() {
+                    @Override
+                    protected Map<String, String> childValue(final Map<String, 
String> parentValue) {
+                        return parentValue != null && useMap
+                                ? Collections.unmodifiableMap(new 
HashMap<>(parentValue))
+                                : null;
+                    }
+                }
+                : new ThreadLocal<Map<String, String>>();
     }
 
     @Override
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java
index 1bf201e162..5ed3fb41ea 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/DefaultThreadContextStack.java
@@ -39,6 +39,14 @@ public class DefaultThreadContextStack implements 
ThreadContextStack, StringBuil
 
     private final boolean useStack;
 
+    public DefaultThreadContextStack() {
+        this(true);
+    }
+
+    /**
+     * @deprecated since 2.24.0 without a replacement.
+     */
+    @Deprecated
     public DefaultThreadContextStack(final boolean useStack) {
         this.useStack = useStack;
     }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
index 0262be3b50..1622a0c455 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/GarbageFreeSortedArrayThreadContextMap.java
@@ -52,42 +52,23 @@ class GarbageFreeSortedArrayThreadContextMap implements 
ReadOnlyThreadContextMap
      */
     protected static final String PROPERTY_NAME_INITIAL_CAPACITY = 
"log4j2.ThreadContext.initial.capacity";
 
+    private final int initialCapacity;
     protected final ThreadLocal<StringMap> localMap;
 
-    private static volatile int initialCapacity;
-    private static volatile boolean inheritableMap;
-
-    /**
-     * Initializes static variables based on system properties. Normally 
called when this class is initialized by the VM
-     * and when Log4j is reconfigured.
-     */
-    static void init() {
-        final PropertiesUtil properties = PropertiesUtil.getProperties();
-        initialCapacity = 
properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, 
DEFAULT_INITIAL_CAPACITY);
-        inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP);
-    }
-
-    static {
-        init();
-    }
-
     public GarbageFreeSortedArrayThreadContextMap() {
-        this.localMap = createThreadLocalMap();
+        this(PropertiesUtil.getProperties());
     }
 
-    // LOG4J2-479: by default, use a plain ThreadLocal, only use 
InheritableThreadLocal if configured.
-    // (This method is package protected for JUnit tests.)
-    private ThreadLocal<StringMap> createThreadLocalMap() {
-        if (inheritableMap) {
-            return new InheritableThreadLocal<StringMap>() {
-                @Override
-                protected StringMap childValue(final StringMap parentValue) {
-                    return parentValue != null ? createStringMap(parentValue) 
: null;
+    GarbageFreeSortedArrayThreadContextMap(final PropertiesUtil properties) {
+        initialCapacity = 
properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, 
DEFAULT_INITIAL_CAPACITY);
+        localMap = properties.getBooleanProperty(INHERITABLE_MAP)
+                ? new InheritableThreadLocal<StringMap>() {
+                    @Override
+                    protected StringMap childValue(final StringMap 
parentValue) {
+                        return parentValue != null ? 
createStringMap(parentValue) : null;
+                    }
                 }
-            };
-        }
-        // if not inheritable, return plain ThreadLocal with null as initial 
value
-        return new ThreadLocal<>();
+                : new ThreadLocal<>();
     }
 
     /**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
index a67ccad231..c1b5e14278 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/Provider.java
@@ -20,39 +20,135 @@ import java.lang.ref.WeakReference;
 import java.net.URL;
 import java.util.Properties;
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
 import org.apache.logging.log4j.status.StatusLogger;
+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.PropertiesUtil;
 
 /**
- * Model class for a Log4j 2 provider. The properties in this class correspond 
to the properties used in a
- * {@code META-INF/log4j-provider.properties} file. Note that this class is 
automatically created by Log4j and should
- * not be used by providers.
+ * Service class used to bind the Log4j API with an implementation.
+ * <p>
+ *     Implementors should register an implementation of this class with 
{@link java.util.ServiceLoader}.
+ * </p>
+ * <p>
+ *     <strong>Deprecated:</strong> the automatic registration of providers 
from
+ *     {@code META-INF/log4j-provider.properties} is supported for 
compatibility reasons. Support for this file will
+ *     be dropped in a future version.
+ * </p>
  */
 public class Provider {
+    protected static final String CURRENT_VERSION = "2.6.0";
+
     /**
      * Property name to set for a Log4j 2 provider to specify the priority of 
this implementation.
+     * @deprecated since 2.24.0
      */
+    @Deprecated
     public static final String FACTORY_PRIORITY = "FactoryPriority";
+
     /**
-     * Property name to set to the implementation of {@link 
org.apache.logging.log4j.spi.ThreadContextMap}.
+     * Property name to set to the implementation of {@link ThreadContextMap}.
+     * @deprecated since 2.24.0
      */
+    @Deprecated
     public static final String THREAD_CONTEXT_MAP = "ThreadContextMap";
+
     /**
-     * Property name to set to the implementation of {@link 
org.apache.logging.log4j.spi.LoggerContextFactory}.
+     * Property name to set to the implementation of {@link 
LoggerContextFactory}.
+     * @deprecated since 2.24.0
      */
+    @Deprecated
     public static final String LOGGER_CONTEXT_FACTORY = "LoggerContextFactory";
 
-    private static final Integer DEFAULT_PRIORITY = Integer.valueOf(-1);
+    /**
+     * System property used to specify the class name of the provider to use.
+     * @since 2.24.0
+     */
+    public static final String PROVIDER_PROPERTY_NAME = "log4j.provider";
+
+    // Bundled context map implementations
+    private static final String BASE = 
"org.apache.logging.log4j.internal.map.";
+
+    /**
+     * Constant used to disable the {@link ThreadContextMap}.
+     * <p>
+     *     <strong>Warning:</strong> the value of this constant does not point 
to a concrete class name.
+     * </p>
+     * @see #getThreadContextMap
+     */
+    public static final String NO_OP_CONTEXT_MAP = BASE + "NoOp";
+
+    /**
+     * Constant used to select a web application-safe implementation of {@link 
ThreadContextMap}.
+     * <p>
+     *     This implementation only binds JRE classes to {@link ThreadLocal} 
variables.
+     * </p>
+     * <p>
+     *     <strong>Warning:</strong> the value of this constant does not point 
to a concrete class name.
+     * </p>
+     * @see #getThreadContextMap
+     */
+    public static final String WEB_APP_CONTEXT_MAP = BASE + "WebApp";
+
+    /**
+     * Constant used to select a copy-on-write implementation of {@link 
ThreadContextMap}.
+     * <p>
+     *     <strong>Warning:</strong> the value of this constant does not point 
to a concrete class name.
+     * </p>
+     * @see #getThreadContextMap
+     */
+    public static final String COPY_ON_WRITE_CONTEXT_MAP = BASE + 
"CopyOnWrite";
+
+    /**
+     * Constant used to select a garbage-free implementation of {@link 
ThreadContextMap}.
+     * <p>
+     *     This implementation must ensure that common operations don't create 
new object instances. The drawback is
+     *     the necessity to bind custom classes to {@link ThreadLocal} 
variables.
+     * </p>
+     * <p>
+     *     <strong>Warning:</strong> the value of this constant does not point 
to a concrete class name.
+     * </p>
+     * @see #getThreadContextMap
+     */
+    public static final String GARBAGE_FREE_CONTEXT_MAP = BASE + "GarbageFree";
+
+    // Property keys relevant for context map selection
+    private static final String DISABLE_CONTEXT_MAP = 
"log4j2.disableThreadContextMap";
+    private static final String DISABLE_THREAD_CONTEXT = 
"log4j2.disableThreadContext";
+    private static final String THREAD_CONTEXT_MAP_PROPERTY = 
"log4j2.threadContextMap";
+    private static final String GC_FREE_THREAD_CONTEXT_PROPERTY = 
"log4j2.garbagefree.threadContextMap";
+
+    private static final Integer DEFAULT_PRIORITY = -1;
     private static final Logger LOGGER = StatusLogger.getLogger();
 
     private final Integer priority;
+    // LoggerContextFactory
+    @Deprecated
     private final String className;
+
     private final Class<? extends LoggerContextFactory> 
loggerContextFactoryClass;
+    private final Lazy<LoggerContextFactory> loggerContextFactoryLazy = 
Lazy.lazy(this::createLoggerContextFactory);
+    // ThreadContextMap
+    @Deprecated
     private final String threadContextMap;
+
     private final Class<? extends ThreadContextMap> threadContextMapClass;
+    private final Lazy<ThreadContextMap> threadContextMapLazy = 
Lazy.lazy(this::createThreadContextMap);
     private final String versions;
+
+    @Deprecated
     private final URL url;
+
+    @Deprecated
     private final WeakReference<ClassLoader> classLoader;
 
+    /**
+     * Constructor used by the deprecated {@code 
META-INF/log4j-provider.properties} format.
+     * @deprecated since 2.24.0
+     */
+    @Deprecated
     public Provider(final Properties props, final URL url, final ClassLoader 
classLoader) {
         this.url = url;
         this.classLoader = new WeakReference<>(classLoader);
@@ -65,6 +161,21 @@ public class Provider {
         versions = null;
     }
 
+    /**
+     * @param priority A positive number specifying the provider's priority or 
{@code null} if default,
+     * @param versions Minimal API version required, should be set to {@link 
#CURRENT_VERSION}.
+     * @since 2.24.0
+     */
+    public Provider(final Integer priority, final String versions) {
+        this(priority, versions, null, null);
+    }
+
+    /**
+     * @param priority A positive number specifying the provider's priority or 
{@code null} if default,
+     * @param versions Minimal API version required, should be set to {@link 
#CURRENT_VERSION},
+     * @param loggerContextFactoryClass A public exported implementation of 
{@link LoggerContextFactory} or {@code
+     * null} if {@link #createLoggerContextFactory()} is also implemented.
+     */
     public Provider(
             final Integer priority,
             final String versions,
@@ -72,19 +183,28 @@ public class Provider {
         this(priority, versions, loggerContextFactoryClass, null);
     }
 
+    /**
+     * @param priority A positive number specifying the provider's priority or 
{@code null} if default,
+     * @param versions Minimal API version required, should be set to {@link 
#CURRENT_VERSION},
+     * @param loggerContextFactoryClass A public exported implementation of 
{@link LoggerContextFactory} or {@code
+     * null} if {@link #createLoggerContextFactory()} is also implemented,
+     * @param threadContextMapClass A public exported implementation of {@link 
ThreadContextMap} or {@code null} if
+     * {@link #createThreadContextMap()} is implemented.
+     */
     public Provider(
             final Integer priority,
             final String versions,
             final Class<? extends LoggerContextFactory> 
loggerContextFactoryClass,
             final Class<? extends ThreadContextMap> threadContextMapClass) {
-        this.url = null;
-        this.classLoader = null;
-        this.priority = priority;
+        this.priority = priority != null ? priority : DEFAULT_PRIORITY;
+        this.versions = versions;
         this.loggerContextFactoryClass = loggerContextFactoryClass;
         this.threadContextMapClass = threadContextMapClass;
-        this.className = null;
-        this.threadContextMap = null;
-        this.versions = versions;
+        // Deprecated
+        className = null;
+        threadContextMap = null;
+        url = null;
+        classLoader = new WeakReference<>(null);
     }
 
     /**
@@ -97,7 +217,9 @@ public class Provider {
 
     /**
      * Gets the priority (natural ordering) of this Provider.
-     *
+     * <p>
+     *     Log4j selects the highest priority provider.
+     * </p>
      * @return the priority of this Provider
      */
     public Integer getPriority() {
@@ -105,119 +227,254 @@ public class Provider {
     }
 
     /**
-     * Gets the class name of the {@link 
org.apache.logging.log4j.spi.LoggerContextFactory} implementation of this
-     * Provider.
+     * Gets the class name of the {@link LoggerContextFactory} implementation 
of this Provider.
      *
-     * @return the class name of a LoggerContextFactory implementation
+     * @return the class name of a LoggerContextFactory implementation or 
{@code null} if unspecified.
+     * @see #loadLoggerContextFactory()
      */
     public String getClassName() {
-        if (loggerContextFactoryClass != null) {
-            return loggerContextFactoryClass.getName();
-        }
-        return className;
+        return loggerContextFactoryClass != null ? 
loggerContextFactoryClass.getName() : className;
     }
 
     /**
-     * Loads the {@link org.apache.logging.log4j.spi.LoggerContextFactory} 
class specified by this Provider.
+     * Loads the {@link LoggerContextFactory} class specified by this Provider.
      *
-     * @return the LoggerContextFactory implementation class or {@code null} 
if there was an error loading it
+     * @return the LoggerContextFactory implementation class or {@code null} 
if unspecified or a loader error occurred.
+     * @see #createLoggerContextFactory()
      */
     public Class<? extends LoggerContextFactory> loadLoggerContextFactory() {
         if (loggerContextFactoryClass != null) {
             return loggerContextFactoryClass;
         }
-        if (className == null) {
-            return null;
-        }
+        final String className = getClassName();
         final ClassLoader loader = classLoader.get();
-        if (loader == null) {
+        // Support for deprecated {@code META-INF/log4j-provider.properties} 
format.
+        // In the remaining cases {@code loader == null}.
+        if (loader == null || className == null) {
             return null;
         }
         try {
             final Class<?> clazz = loader.loadClass(className);
             if (LoggerContextFactory.class.isAssignableFrom(clazz)) {
                 return clazz.asSubclass(LoggerContextFactory.class);
+            } else {
+                LOGGER.error(
+                        "Class {} specified in {} does not extend {}",
+                        className,
+                        getUrl(),
+                        LoggerContextFactory.class.getName());
             }
         } catch (final Exception e) {
-            LOGGER.error("Unable to create class {} specified in {}", 
className, url.toString(), e);
+            LOGGER.error("Unable to create class {} specified in {}", 
className, getUrl(), e);
         }
         return null;
     }
 
     /**
-     * Gets the class name of the {@link 
org.apache.logging.log4j.spi.ThreadContextMap} implementation of this Provider.
-     *
+     * Extension point for providers to create a {@link LoggerContextFactory}.
+     * @since 2.24.0
+     */
+    protected LoggerContextFactory createLoggerContextFactory() {
+        final Class<? extends LoggerContextFactory> factoryClass = 
loadLoggerContextFactory();
+        if (factoryClass != null) {
+            try {
+                return LoaderUtil.newInstanceOf(factoryClass);
+            } catch (final Exception e) {
+                LOGGER.error(
+                        "Unable to create instance of class {} specified in 
{}", factoryClass.getName(), getUrl(), e);
+            }
+        }
+        LOGGER.warn("Falling back to {}", SimpleLoggerContextFactory.INSTANCE);
+        return SimpleLoggerContextFactory.INSTANCE;
+    }
+
+    /**
+     * @return A lazily initialized logger context factory
+     * @since 2.24.0
+     */
+    public final LoggerContextFactory getLoggerContextFactory() {
+        return loggerContextFactoryLazy.get();
+    }
+
+    /**
+     * Gets the class name of the {@link ThreadContextMap} implementation of 
this Provider.
+     * <p>
+     *     This method should return one of the internal implementations:
+     *     <ol>
+     *         <li>{@code null} if {@link #loadThreadContextMap} is 
implemented,</li>
+     *         <li>{@link #NO_OP_CONTEXT_MAP},</li>
+     *         <li>{@link #WEB_APP_CONTEXT_MAP},</li>
+     *         <li>{@link #COPY_ON_WRITE_CONTEXT_MAP},</li>
+     *         <li>{@link #GARBAGE_FREE_CONTEXT_MAP}.</li>
+     *     </ol>
+     * </p>
      * @return the class name of a ThreadContextMap implementation
+     * @see #loadThreadContextMap()
      */
     public String getThreadContextMap() {
         if (threadContextMapClass != null) {
             return threadContextMapClass.getName();
         }
-        return threadContextMap;
+        // Field value
+        if (threadContextMap != null) {
+            return threadContextMap;
+        }
+        // Properties
+        final PropertiesUtil props = PropertiesUtil.getProperties();
+        if (props.getBooleanProperty(DISABLE_CONTEXT_MAP) || 
props.getBooleanProperty(DISABLE_THREAD_CONTEXT)) {
+            return NO_OP_CONTEXT_MAP;
+        }
+        final String threadContextMapClass = 
props.getStringProperty(THREAD_CONTEXT_MAP_PROPERTY);
+        if (threadContextMapClass != null) {
+            return threadContextMapClass;
+        }
+        // Default based on properties
+        if (Constants.ENABLE_THREADLOCALS) {
+            return props.getBooleanProperty(GC_FREE_THREAD_CONTEXT_PROPERTY)
+                    ? GC_FREE_THREAD_CONTEXT_PROPERTY
+                    : COPY_ON_WRITE_CONTEXT_MAP;
+        }
+        return WEB_APP_CONTEXT_MAP;
     }
 
     /**
-     * Loads the {@link org.apache.logging.log4j.spi.ThreadContextMap} class 
specified by this Provider.
+     * Loads the {@link ThreadContextMap} class specified by this Provider.
      *
-     * @return the ThreadContextMap implementation class or {@code null} if 
there was an error loading it
+     * @return the {@code ThreadContextMap} implementation class or {@code 
null} if unspecified or a loading error
+     * occurred.
+     * @see #createThreadContextMap()
      */
     public Class<? extends ThreadContextMap> loadThreadContextMap() {
         if (threadContextMapClass != null) {
             return threadContextMapClass;
         }
-        if (threadContextMap == null) {
-            return null;
-        }
+        final String threadContextMap = getThreadContextMap();
         final ClassLoader loader = classLoader.get();
-        if (loader == null) {
+        // Support for deprecated {@code META-INF/log4j-provider.properties} 
format.
+        // In the remaining cases {@code loader == null}.
+        if (loader == null || threadContextMap == null) {
             return null;
         }
         try {
             final Class<?> clazz = loader.loadClass(threadContextMap);
             if (ThreadContextMap.class.isAssignableFrom(clazz)) {
                 return clazz.asSubclass(ThreadContextMap.class);
+            } else {
+                LOGGER.error(
+                        "Class {} specified in {} does not extend {}",
+                        threadContextMap,
+                        getUrl(),
+                        ThreadContextMap.class.getName());
             }
         } catch (final Exception e) {
-            LOGGER.error("Unable to create class {} specified in {}", 
threadContextMap, url.toString(), e);
+            LOGGER.error("Unable to load class {} specified in {}", 
threadContextMap, url.toString(), e);
         }
         return null;
     }
 
+    /**
+     * Extension point for providers to create a {@link ThreadContextMap}
+     * @implNote The default implementation:
+     * <ol>
+     *     <li>calls {@link #loadThreadContextMap},</li>
+     *     <li>if the previous call returns {@code null}, it calls {@link 
#getThreadContextMap} to instantiate one of
+     *     the internal implementations,</li>
+     *     <li>it returns a no-op map otherwise.</li>
+     * </ol>
+     * @since 2.24.0
+     */
+    protected ThreadContextMap createThreadContextMap() {
+        final Class<? extends ThreadContextMap> threadContextMapClass = 
loadThreadContextMap();
+        if (threadContextMapClass != null) {
+            try {
+                return LoaderUtil.newInstanceOf(threadContextMapClass);
+            } catch (final Exception e) {
+                LOGGER.error(
+                        "Unable to create instance of class {} specified in 
{}",
+                        threadContextMapClass.getName(),
+                        getUrl(),
+                        e);
+            }
+        }
+        // Standard Log4j API implementations are internal and can be only 
specified by name:
+        final String threadContextMap = getThreadContextMap();
+        if (threadContextMap != null) {
+            /*
+             * The constructors are called explicitly to improve GraalVM 
support.
+             *
+             * The class names of the package-private implementations from 
version 2.23.1 must be recognized even
+             * if the class is moved.
+             */
+            switch (threadContextMap) {
+                case NO_OP_CONTEXT_MAP:
+                case "org.apache.logging.log4j.spi.NoOpThreadContextMap":
+                    return new NoOpThreadContextMap();
+                case WEB_APP_CONTEXT_MAP:
+                case "org.apache.logging.log4j.spi.DefaultThreadContextMap":
+                    return new DefaultThreadContextMap();
+                case GARBAGE_FREE_CONTEXT_MAP:
+                case 
"org.apache.logging.log4j.spi.GarbageFreeSortedArrayThreadContextMap":
+                    return new GarbageFreeSortedArrayThreadContextMap();
+                case COPY_ON_WRITE_CONTEXT_MAP:
+                case 
"org.apache.logging.log4j.spi.CopyOnWriteSortedArrayThreadContextMap":
+                    return new CopyOnWriteSortedArrayThreadContextMap();
+            }
+        }
+        LOGGER.warn("Falling back to {}", 
NoOpThreadContextMap.class.getName());
+        return new NoOpThreadContextMap();
+    }
+
+    // Used for testing
+    void resetThreadContextMap() {
+        threadContextMapLazy.set(null);
+    }
+
+    /**
+     * @return A lazily initialized thread context map.
+     * @since 2.24.0
+     */
+    public final ThreadContextMap getThreadContextMapInstance() {
+        return threadContextMapLazy.get();
+    }
+
     /**
      * Gets the URL containing this Provider's Log4j details.
      *
-     * @return the URL corresponding to the Provider {@code 
META-INF/log4j-provider.properties} file
+     * @return the URL corresponding to the Provider {@code 
META-INF/log4j-provider.properties} file or {@code null}
+     * for a provider class.
+     * @deprecated since 2.24.0, without replacement.
      */
+    @Deprecated
     public URL getUrl() {
         return url;
     }
 
     @Override
     public String toString() {
-        final StringBuilder result = new StringBuilder("Provider[");
+        final StringBuilder result =
+                new StringBuilder("Provider 
'").append(getClass().getName()).append("'");
         if (!DEFAULT_PRIORITY.equals(priority)) {
-            result.append("priority=").append(priority).append(", ");
+            result.append("\n\tpriority             = ").append(priority);
         }
+        final String threadContextMap = getThreadContextMap();
         if (threadContextMap != null) {
-            
result.append("threadContextMap=").append(threadContextMap).append(", ");
-        } else if (threadContextMapClass != null) {
-            
result.append("threadContextMapClass=").append(threadContextMapClass.getName());
+            result.append("\n\tthreadContextMap     = 
").append(threadContextMap);
         }
-        if (className != null) {
-            result.append("className=").append(className).append(", ");
-        } else if (loggerContextFactoryClass != null) {
-            
result.append("class=").append(loggerContextFactoryClass.getName());
+        final String loggerContextFactory = getClassName();
+        if (loggerContextFactory != null) {
+            result.append("\n\tloggerContextFactory = 
").append(loggerContextFactory);
         }
         if (url != null) {
-            result.append("url=").append(url);
+            result.append("\n\turl                  = ").append(url);
         }
-        final ClassLoader loader;
-        if (classLoader == null || (loader = classLoader.get()) == null) {
-            result.append(", classLoader=null(not reachable)");
-        } else {
-            result.append(", classLoader=").append(loader);
+        if (Provider.class.equals(getClass())) {
+            final ClassLoader loader = classLoader.get();
+            if (loader == null) {
+                result.append("\n\tclassLoader          = null or not 
reachable");
+            } else {
+                result.append("\n\tclassLoader          = ").append(loader);
+            }
         }
-        result.append("]");
         return result.toString();
     }
 
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java
index 566ae1463c..b00bd97985 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ThreadContextMapFactory.java
@@ -16,13 +16,8 @@
  */
 package org.apache.logging.log4j.spi;
 
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.Constants;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.ProviderUtil;
 
 /**
@@ -47,102 +42,18 @@ import org.apache.logging.log4j.util.ProviderUtil;
  * @since 2.7
  */
 public final class ThreadContextMapFactory {
-    private static final Logger LOGGER = StatusLogger.getLogger();
-    private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap";
-    private static final String GC_FREE_THREAD_CONTEXT_KEY = 
"log4j2.garbagefree.threadContextMap";
-
-    private static boolean GcFreeThreadContextKey;
-    private static String ThreadContextMapName;
-    private static final String GARBAGE_FREE_CONTEXT_MAP =
-            "org.apache.logging.log4j.spi" + 
".GarbageFreeSortedArrayThreadContextMap";
-    private static final String COPY_ON_WRITE_CONTEXT_MAP =
-            "org.apache.logging.log4j.spi" + 
".CopyOnWriteSortedArrayThreadContextMap";
-
-    static {
-        initPrivate();
-    }
 
     /**
      * Initializes static variables based on system properties. Normally 
called when this class is initialized by the VM
      * and when Log4j is reconfigured.
      */
     public static void init() {
-        CopyOnWriteSortedArrayThreadContextMap.init();
-        GarbageFreeSortedArrayThreadContextMap.init();
-        DefaultThreadContextMap.init();
-        initPrivate();
-    }
-
-    /**
-     * Initializes static variables based on system properties. Normally 
called when this class is initialized by the VM
-     * and when Log4j is reconfigured.
-     */
-    private static void initPrivate() {
-        final PropertiesUtil properties = PropertiesUtil.getProperties();
-        ThreadContextMapName = 
properties.getStringProperty(THREAD_CONTEXT_KEY);
-        GcFreeThreadContextKey = 
properties.getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY);
+        ProviderUtil.getProvider().resetThreadContextMap();
     }
 
     private ThreadContextMapFactory() {}
 
     public static ThreadContextMap createThreadContextMap() {
-        final ClassLoader cl = ProviderUtil.findClassLoader();
-        ThreadContextMap result = null;
-        if (ThreadContextMapName != null) {
-            /*
-             * Two implementation are package-private classes, so we 
instantiate them directly.
-             * Other implementation must be publicly accessible (through 
`LoaderUtil`).
-             */
-            switch (ThreadContextMapName) {
-                case GARBAGE_FREE_CONTEXT_MAP:
-                    result = new GarbageFreeSortedArrayThreadContextMap();
-                    break;
-                case COPY_ON_WRITE_CONTEXT_MAP:
-                    result = new CopyOnWriteSortedArrayThreadContextMap();
-                    break;
-                default:
-                    try {
-                        result = 
LoaderUtil.newCheckedInstanceOf(ThreadContextMapName, ThreadContextMap.class);
-                    } catch (final ClassNotFoundException cnfe) {
-                        LOGGER.error("Unable to locate configured 
ThreadContextMap {}", ThreadContextMapName);
-                    } catch (final Exception ex) {
-                        LOGGER.error("Unable to create configured 
ThreadContextMap {}", ThreadContextMapName, ex);
-                    }
-            }
-        }
-        if (result == null && ProviderUtil.hasProviders() && 
LogManager.getFactory() != null) { // LOG4J2-1658
-            final String factoryClassName = 
LogManager.getFactory().getClass().getName();
-            for (final Provider provider : ProviderUtil.getProviders()) {
-                if (factoryClassName.equals(provider.getClassName())) {
-                    final Class<? extends ThreadContextMap> clazz = 
provider.loadThreadContextMap();
-                    if (clazz != null) {
-                        try {
-                            result = LoaderUtil.newInstanceOf(clazz);
-                            break;
-                        } catch (final Exception e) {
-                            LOGGER.error(
-                                    "Unable to locate or load configured 
ThreadContextMap {}",
-                                    provider.getThreadContextMap(),
-                                    e);
-                            result = createDefaultThreadContextMap();
-                        }
-                    }
-                }
-            }
-        }
-        if (result == null) {
-            result = createDefaultThreadContextMap();
-        }
-        return result;
-    }
-
-    private static ThreadContextMap createDefaultThreadContextMap() {
-        if (Constants.ENABLE_THREADLOCALS) {
-            if (GcFreeThreadContextKey) {
-                return new GarbageFreeSortedArrayThreadContextMap();
-            }
-            return new CopyOnWriteSortedArrayThreadContextMap();
-        }
-        return new DefaultThreadContextMap(true);
+        return ProviderUtil.getProvider().createThreadContextMap();
     }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java
index eeba052486..ca7c4c7178 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ProviderUtil.java
@@ -16,31 +16,40 @@
  */
 package org.apache.logging.log4j.util;
 
+import static org.apache.logging.log4j.LogManager.FACTORY_PROPERTY_NAME;
+import static org.apache.logging.log4j.spi.Provider.PROVIDER_PROPERTY_NAME;
+
 import aQute.bnd.annotation.Cardinality;
 import aQute.bnd.annotation.Resolution;
-import aQute.bnd.annotation.baseline.BaselineIgnore;
 import aQute.bnd.annotation.spi.ServiceConsumer;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.net.URL;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Properties;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.apache.logging.log4j.spi.NoOpThreadContextMap;
 import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.status.StatusLogger;
 
 /**
- * <em>Consider this class private.</em> Utility class for Log4j {@link 
Provider}s. When integrating with an application
- * container framework, any Log4j Providers not accessible through standard 
classpath scanning should
- * {@link #loadProvider(java.net.URL, ClassLoader)} a classpath accordingly.
+ * <em>Consider this class private.</em>
+ * <p>
+ *     Utility class for Log4j {@link Provider}s. When integrating with an 
application container framework, any Log4j
+ *     Providers not accessible through standard classpath scanning should
+ *     {@link #loadProvider(java.net.URL, ClassLoader)} a classpath 
accordingly.
+ * </p>
  */
 @InternalApi
-@BaselineIgnore("2.22.0")
 @ServiceConsumer(value = Provider.class, resolution = Resolution.OPTIONAL, 
cardinality = Cardinality.MULTIPLE)
 public final class ProviderUtil {
 
@@ -55,9 +64,11 @@ public final class ProviderUtil {
     static final Collection<Provider> PROVIDERS = new HashSet<>();
 
     /**
-     * Guards the ProviderUtil singleton instance from lazy initialization. 
This is primarily used for OSGi support.
-     *
-     * @since 2.1
+     * Guards the ProviderUtil singleton instance from lazy initialization.
+     * <p>
+     *     This is primarily used for OSGi support. It allows the OSGi 
Activator to pause the startup and wait for a
+     *     Provider to be installed. See <a 
href="https://issues.apache.org/jira/browse/LOG4J2-373";>LOG4J2-373</a>.
+     * </p>
      */
     static final Lock STARTUP_LOCK = new ReentrantLock();
 
@@ -65,19 +76,10 @@ public final class ProviderUtil {
     private static final String[] COMPATIBLE_API_VERSIONS = {"2.6.0"};
     private static final Logger LOGGER = StatusLogger.getLogger();
 
-    // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the 
OSGi Activator to pause the startup and
-    // wait for a Provider to be installed. See LOG4J2-373
-    private static volatile ProviderUtil instance;
-
-    private ProviderUtil() {
-        ServiceLoaderUtil.loadServices(Provider.class, MethodHandles.lookup(), 
false)
-                .filter(provider -> validVersion(provider.getVersions()))
-                .forEach(PROVIDERS::add);
+    private static volatile Provider PROVIDER;
+    private static final Provider FALLBACK_PROVIDER = new SimpleProvider();
 
-        for (final LoaderUtil.UrlResource resource : 
LoaderUtil.findUrlResources(PROVIDER_RESOURCE, false)) {
-            loadProvider(resource.getUrl(), resource.getClassLoader());
-        }
-    }
+    private ProviderUtil() {}
 
     static void addProvider(final Provider provider) {
         PROVIDERS.add(provider);
@@ -107,16 +109,6 @@ public final class ProviderUtil {
         }
     }
 
-    /**
-     *
-     * @param classLoader null can be used to mark the bootstrap class loader.
-     */
-    static void loadProviders(final ClassLoader classLoader) {
-        ServiceLoaderUtil.loadClassloaderServices(Provider.class, 
MethodHandles.lookup(), classLoader, true)
-                .filter(provider -> validVersion(provider.getVersions()))
-                .forEach(PROVIDERS::add);
-    }
-
     /**
      * @deprecated Use {@link #loadProvider(java.net.URL, ClassLoader)} 
instead. Will be removed in 3.0.
      */
@@ -129,6 +121,14 @@ public final class ProviderUtil {
         }
     }
 
+    /**
+     * @since 2.24.0
+     */
+    public static Provider getProvider() {
+        lazyInit();
+        return PROVIDER;
+    }
+
     public static Iterable<Provider> getProviders() {
         lazyInit();
         return PROVIDERS;
@@ -141,17 +141,26 @@ public final class ProviderUtil {
 
     /**
      * Lazily initializes the ProviderUtil singleton.
-     *
-     * @since 2.1
+     * <p>
+     *     Note that the following initial call to ProviderUtil may block 
until a Provider has been installed when
+     *     running in an OSGi environment.
+     * </p>
      */
     static void lazyInit() {
-        // noinspection DoubleCheckedLocking
-        if (instance == null) {
+        if (PROVIDER == null) {
             try {
                 STARTUP_LOCK.lockInterruptibly();
                 try {
-                    if (instance == null) {
-                        instance = new ProviderUtil();
+                    if (PROVIDER == null) {
+                        ServiceLoaderUtil.loadServices(Provider.class, 
MethodHandles.lookup(), false)
+                                .filter(provider -> 
validVersion(provider.getVersions()))
+                                .forEach(PROVIDERS::add);
+
+                        for (final LoaderUtil.UrlResource resource :
+                                LoaderUtil.findUrlResources(PROVIDER_RESOURCE, 
false)) {
+                            loadProvider(resource.getUrl(), 
resource.getClassLoader());
+                        }
+                        PROVIDER = 
selectProvider(PropertiesUtil.getProperties());
                     }
                 } finally {
                     STARTUP_LOCK.unlock();
@@ -163,6 +172,88 @@ public final class ProviderUtil {
         }
     }
 
+    /**
+     * Used to test the public {@link #getProvider()} method.
+     */
+    static Provider selectProvider(final PropertiesUtil properties) {
+        Provider selected = null;
+        // 1. Select provider using "log4j.provider" property
+        final String providerClass = 
properties.getStringProperty(PROVIDER_PROPERTY_NAME);
+        if (providerClass != null) {
+            try {
+                selected = LoaderUtil.newInstanceOf(providerClass);
+            } catch (final Exception e) {
+                LOGGER.error("Unable to create provider {}.\nFalling back to 
default selection process.", PROVIDER, e);
+            }
+        }
+        // 2. Use deprecated "log4j2.loggerContextFactory" property to choose 
the provider
+        final String factoryClassName = 
properties.getStringProperty(FACTORY_PROPERTY_NAME);
+        if (factoryClassName != null) {
+            if (selected != null) {
+                LOGGER.warn(
+                        "Ignoring {} system property, since {} was set.",
+                        FACTORY_PROPERTY_NAME,
+                        PROVIDER_PROPERTY_NAME);
+                // 2a. Scan the known providers for one matching the logger 
context factory class name.
+            } else {
+                LOGGER.warn(
+                        "Usage of the {} property is deprecated. Use the {} 
property instead.",
+                        FACTORY_PROPERTY_NAME,
+                        PROVIDER_PROPERTY_NAME);
+                for (final Provider provider : PROVIDERS) {
+                    if (factoryClassName.equals(provider.getClassName())) {
+                        selected = provider;
+                        break;
+                    }
+                }
+            }
+            // 2b. Instantiate
+            if (selected == null) {
+                LOGGER.warn(
+                        "No provider found using {} as logger context factory. 
The factory will be instantiated directly.",
+                        factoryClassName);
+                try {
+                    final Class<?> clazz = 
LoaderUtil.loadClass(factoryClassName);
+                    if (LoggerContextFactory.class.isAssignableFrom(clazz)) {
+                        selected = new Provider(null, Strings.EMPTY, 
clazz.asSubclass(LoggerContextFactory.class));
+                    } else {
+                        LOGGER.error(
+                                "Class {} specified in the {} system property 
does not extend {}",
+                                factoryClassName,
+                                FACTORY_PROPERTY_NAME,
+                                LoggerContextFactory.class.getName());
+                    }
+                } catch (final Exception e) {
+                    LOGGER.error(
+                            "Unable to create class {} specified in the {} 
system property",
+                            factoryClassName,
+                            FACTORY_PROPERTY_NAME,
+                            e);
+                }
+            }
+        }
+        // 3. Select a provider automatically.
+        if (selected == null) {
+            final Comparator<Provider> comparator = 
Comparator.comparing(Provider::getPriority);
+            switch (PROVIDERS.size()) {
+                case 0:
+                    LOGGER.error("Log4j API could not find a logging 
provider.");
+                    break;
+                case 1:
+                    break;
+                default:
+                    LOGGER.warn(PROVIDERS.stream()
+                            .sorted(comparator)
+                            .map(Provider::toString)
+                            .collect(Collectors.joining("\n", "Log4j API found 
multiple logging providers:\n", "")));
+                    break;
+            }
+            selected = 
PROVIDERS.stream().max(comparator).orElse(FALLBACK_PROVIDER);
+        }
+        LOGGER.info("Using provider:\n{}", selected);
+        return selected;
+    }
+
     public static ClassLoader findClassLoader() {
         return LoaderUtil.getThreadContextClassLoader();
     }
@@ -175,4 +266,10 @@ public final class ProviderUtil {
         }
         return false;
     }
+
+    private static final class SimpleProvider extends Provider {
+        private SimpleProvider() {
+            super(null, CURRENT_VERSION, SimpleLoggerContextFactory.class, 
NoOpThreadContextMap.class);
+        }
+    }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java
index 8265e82149..757248862a 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java
@@ -20,7 +20,7 @@
  * There are no guarantees for binary or logical compatibility in this package.
  */
 @Export
-@Version("2.22.0")
+@Version("2.24.0")
 package org.apache.logging.log4j.util;
 
 import org.osgi.annotation.bundle.Export;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
index 1c3652626a..bd0b62337c 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java
@@ -26,6 +26,6 @@ import org.apache.logging.log4j.spi.Provider;
 @ServiceProvider(value = Provider.class, resolution = Resolution.OPTIONAL)
 public class Log4jProvider extends Provider {
     public Log4jProvider() {
-        super(10, "2.6.0", Log4jContextFactory.class);
+        super(10, CURRENT_VERSION, Log4jContextFactory.class);
     }
 }

Reply via email to