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

pkarwasz pushed a commit to branch fix/move-recycler-to-kit
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 397e2103a5614ad358a7ed98a4c135c9a30089bf
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Thu Mar 14 18:46:58 2024 +0100

    Move `Recycler` to `log4j-kit`
---
 .../log4j/message/ReusableMessageFactory.java      |   2 +-
 .../message/ReusableParameterizedMessage.java      |   2 +-
 .../message/StringFormatterMessageFactory.java     |   2 +-
 .../MessageFactory2Adapter.java}                   | 128 ++++++++-------------
 .../logging/log4j/util/ServiceLoaderUtil.java      |   7 ++
 .../logging/log4j/async/logger/AsyncLogger.java    |   4 +-
 .../org/apache/logging/log4j/core/LoggerTest.java  |   4 +-
 .../logging/log4j/core/util/JsonUtilsTest.java     |  68 -----------
 .../java/org/apache/logging/log4j/core/Logger.java |   2 +-
 .../apache/logging/log4j/core/LoggerContext.java   |  31 ++---
 .../logging/log4j/core/config/Configuration.java   |   2 +-
 .../log4j/core/filter/StructuredDataFilter.java    |   4 +-
 .../logging/log4j/core/impl/DefaultBundle.java     |  40 +++++--
 .../logging/log4j/core/impl/MutableLogEvent.java   |   2 +-
 .../log4j/core/impl/ReusableLogEventFactory.java   |   4 +-
 .../impl/internal}/ReusableMessageFactory.java     |  99 ++++------------
 .../log4j/core/layout/AbstractStringLayout.java    |   4 +-
 .../logging/log4j/core/layout/PatternLayout.java   |   2 +-
 .../log4j/core/layout/StringBuilderEncoder.java    |   2 +-
 .../log4j/core/pattern/DatePatternConverter.java   |   2 +-
 .../apache/logging/log4j/core/util/JsonUtils.java  | 117 -------------------
 .../jctools/JCToolsRecyclerFactoryProvider.java    |  20 ++--
 .../JCToolsRecyclerFactoryProviderTest.java        |  12 +-
 ...ing.log4j.kit.recycler.RecyclerFactoryProvider} |   0
 .../logging/log4j/kit/logger/AbstractLogger.java   |  17 ++-
 .../kit/logger/internal/DefaultLogBuilder.java     |   4 +-
 .../log4j/kit/message/RecyclingMessageFactory.java |  36 +++---
 .../logging/log4j/kit/message/package-info.java    |  22 ++++
 .../logging/log4j/kit/recycler/Recycler.java       |  48 ++++++++
 .../logging/log4j/kit/recycler/RecyclerAware.java  |  27 ++---
 .../log4j/kit/recycler/RecyclerFactory.java        |  58 ++++++++++
 .../kit/recycler/RecyclerFactoryProvider.java      |  64 +++++++++++
 .../log4j/kit/recycler/internal/ArrayQueue.java    | 100 ++++++++++++++++
 .../internal/DummyRecyclerFactoryProvider.java     |  83 +++++++++++++
 .../internal/QueueingRecyclerFactoryProvider.java  |  50 ++++----
 .../ThreadLocalRecyclerFactoryProvider.java        | 123 ++++++++++++++++++++
 .../logging/log4j/kit/recycler/package-info.java   |  26 +++++
 .../kit/recycler/support/AbstractRecycler.java     |  48 ++++++++
 .../log4j/kit/recycler/support/package-info.java   |  25 ++++
 .../log4j/kit/env/TestPropertyEnvironment.java     |  34 +++---
 .../env/support/BasicPropertyEnvironmentTest.java  |  22 +---
 .../logging/log4j/kit/logger/TestListLogger.java   |   4 +-
 .../kit/recycler/internal/ArrayQueueTest.java      | 101 ++++++++++++++++
 .../internal/RecyclerFactoryRegistryTest.java      |  84 ++++++++++++++
 .../recycler/internal/RecyclerFactoryTestUtil.java |  51 ++++++++
 .../ThreadLocalRecyclerFactoryProviderTest.java    | 105 +++++++++++++++++
 .../layout/template/json/JsonTemplateLayout.java   |   2 +-
 .../template/json/resolver/CounterResolver.java    |   2 +-
 .../json/resolver/MessageParameterResolver.java    |   2 +-
 .../json/resolver/ReadOnlyStringMapResolver.java   |   4 +-
 .../json/resolver/StackTraceStringResolver.java    |   4 +-
 .../services/org.apache.logging.log4j.spi.Provider |   1 -
 52 files changed, 1203 insertions(+), 504 deletions(-)

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 6ec36d5308..93c0f27f63 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
@@ -32,7 +32,7 @@ import org.apache.logging.log4j.util.PerformanceSensitive;
  * @since 2.6
  */
 @PerformanceSensitive("allocation")
-public final class ReusableMessageFactory implements MessageFactory {
+public final class ReusableMessageFactory implements MessageFactory2 {
 
     /**
      * Instance of {@link ReusableMessageFactory}.
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
index ea074c5db1..166fd213c8 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableParameterizedMessage.java
@@ -152,7 +152,7 @@ public class ReusableParameterizedMessage implements 
ReusableMessage, ParameterV
         return null;
     }
 
-    protected ReusableParameterizedMessage set(final String messagePattern, 
final Object... arguments) {
+    public ReusableParameterizedMessage set(final String messagePattern, final 
Object... arguments) {
         init(messagePattern, arguments == null ? 0 : arguments.length, 
arguments);
         varargs = arguments;
         return this;
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
index 7ce4296ba5..855340a197 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
@@ -34,7 +34,7 @@ package org.apache.logging.log4j.message;
  * This class implements all {@link MessageFactory} methods.
  * </p>
  */
-public final class StringFormatterMessageFactory implements MessageFactory {
+public final class StringFormatterMessageFactory implements MessageFactory2 {
 
     /**
      * Instance of StringFormatterMessageFactory.
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
similarity index 57%
copy from 
log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
copy to 
log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
index 7ce4296ba5..1e0f4162c0 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
@@ -14,97 +14,62 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.message;
+package org.apache.logging.log4j.spi;
+
+import java.util.Objects;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.MessageFactory2;
+import org.apache.logging.log4j.message.SimpleMessage;
 
 /**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory} 
methods.
- * <p>
- * Enables the use of {@link java.util.Formatter} strings in message strings.
- * </p>
- * <p>
- * Creates {@link StringFormattedMessage} 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>
+ * Adapts a legacy MessageFactory to the new MessageFactory2 interface.
+ *
+ * @since 2.6
  */
-public final class StringFormatterMessageFactory implements MessageFactory {
-
-    /**
-     * Instance of StringFormatterMessageFactory.
-     */
-    public static final StringFormatterMessageFactory INSTANCE = new 
StringFormatterMessageFactory();
-
-    /**
-     * Constructs a message factory with default flow strings.
-     */
-    public StringFormatterMessageFactory() {}
-
-    /**
-     * Creates {@link StringFormattedMessage} instances.
-     *
-     * @param message The message pattern.
-     * @param params The parameters to the message.
-     * @return The Message.
-     *
-     * @see MessageFactory#newMessage(String, Object...)
-     */
+public class MessageFactory2Adapter implements MessageFactory2 {
+    private final MessageFactory wrapped;
+
+    public MessageFactory2Adapter(final MessageFactory wrapped) {
+        this.wrapped = Objects.requireNonNull(wrapped);
+    }
+
+    public MessageFactory getOriginal() {
+        return wrapped;
+    }
+
     @Override
-    public Message newMessage(final String message, final Object... params) {
-        return new StringFormattedMessage(message, params);
+    public Message newMessage(final CharSequence charSequence) {
+        return new SimpleMessage(charSequence);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(final String message, final Object p0) {
-        return new StringFormattedMessage(message, p0);
+        return wrapped.newMessage(message, p0);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(final String message, final Object p0, final 
Object p1) {
-        return new StringFormattedMessage(message, p0, p1);
+        return wrapped.newMessage(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 StringFormattedMessage(message, p0, p1, p2);
+        return wrapped.newMessage(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 StringFormattedMessage(message, p0, p1, p2, p3);
+        return wrapped.newMessage(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 StringFormattedMessage(message, p0, p1, p2, p3, p4);
+        return wrapped.newMessage(message, p0, p1, p2, p3, p4);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(
             final String message,
@@ -114,12 +79,9 @@ public final class StringFormatterMessageFactory implements 
MessageFactory {
             final Object p3,
             final Object p4,
             final Object p5) {
-        return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5);
+        return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(
             final String message,
@@ -130,12 +92,9 @@ public final class StringFormatterMessageFactory implements 
MessageFactory {
             final Object p4,
             final Object p5,
             final Object p6) {
-        return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6);
+        return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(
             final String message,
@@ -147,12 +106,9 @@ public final class StringFormatterMessageFactory 
implements MessageFactory {
             final Object p5,
             final Object p6,
             final Object p7) {
-        return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, 
p7);
+        return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(
             final String message,
@@ -165,12 +121,9 @@ public final class StringFormatterMessageFactory 
implements MessageFactory {
             final Object p6,
             final Object p7,
             final Object p8) {
-        return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, 
p7, p8);
+        return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
     }
 
-    /**
-     * @since 2.6.1
-     */
     @Override
     public Message newMessage(
             final String message,
@@ -184,6 +137,21 @@ public final class StringFormatterMessageFactory 
implements MessageFactory {
             final Object p7,
             final Object p8,
             final Object p9) {
-        return new StringFormattedMessage(message, p0, p1, p2, p3, p4, p5, p6, 
p7, p8, p9);
+        return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, 
p9);
+    }
+
+    @Override
+    public Message newMessage(final Object message) {
+        return wrapped.newMessage(message);
+    }
+
+    @Override
+    public Message newMessage(final String message) {
+        return wrapped.newMessage(message);
+    }
+
+    @Override
+    public Message newMessage(final String message, final Object... params) {
+        return wrapped.newMessage(message, params);
     }
 }
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
index 7fe952cbd0..3302c65323 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
+++ 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/ServiceLoaderUtil.java
@@ -28,6 +28,7 @@ import java.util.Spliterators;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
+import org.apache.logging.log4j.Logger;
 
 /**
  * Handles {@link ServiceLoader} lookups with better error handling.
@@ -54,6 +55,12 @@ public final class ServiceLoaderUtil {
                 .filter(service -> classes.add(service.getClass()));
     }
 
+    // Available in Log4j API 2.x
+    public static <S> Stream<S> safeStream(
+            final Class<S> ignoredServiceType, final ServiceLoader<S> 
serviceLoader, final Logger ignoredStatusLogger) {
+        return safeStream(serviceLoader);
+    }
+
     private static class ServiceLoaderSpliterator<S> extends 
Spliterators.AbstractSpliterator<S> {
         private final Iterator<S> serviceIterator;
         private final String serviceName;
diff --git 
a/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
 
b/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
index 80578ee369..cd430f94c0 100644
--- 
a/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
+++ 
b/log4j-async-logger/src/main/java/org/apache/logging/log4j/async/logger/AsyncLogger.java
@@ -34,13 +34,13 @@ import 
org.apache.logging.log4j.core.impl.ContextDataFactory;
 import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.kit.logger.AbstractLogger;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.StringMap;
 import org.jspecify.annotations.NullMarked;
 import org.jspecify.annotations.Nullable;
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 49f3058956..e4c5c95dfe 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
@@ -51,7 +51,6 @@ 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;
-import org.apache.logging.log4j.spi.LoggingSystem;
 import org.awaitility.Awaitility;
 import org.hamcrest.MatcherAssert;
 import org.junit.jupiter.api.Tag;
@@ -65,7 +64,8 @@ public class LoggerTest {
 
     private static void checkMessageFactory(final MessageFactory 
messageFactory, final Logger testLogger) {
         if (messageFactory == null) {
-            assertSame(LoggingSystem.getMessageFactory(), 
testLogger.getMessageFactory());
+            final org.apache.logging.log4j.Logger newLogger = 
LogManager.getLogger("checkMessageFactory");
+            assertSame(newLogger.getMessageFactory(), 
testLogger.getMessageFactory());
         } else {
             final MessageFactory actual = testLogger.getMessageFactory();
             assertEquals(messageFactory, actual);
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java
deleted file mode 100644
index 5f035e6239..0000000000
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/JsonUtilsTest.java
+++ /dev/null
@@ -1,68 +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.util;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * This class is borrowed from <a 
href="https://github.com/FasterXML/jackson-core";>Jackson</a>.
- */
-public class JsonUtilsTest {
-
-    @Test
-    public void testQuoteCharSequenceAsString() throws Exception {
-        final StringBuilder output = new StringBuilder();
-        final StringBuilder builder = new StringBuilder();
-        builder.append("foobar");
-        JsonUtils.quoteAsString(builder, output);
-        assertEquals("foobar", output.toString());
-        builder.setLength(0);
-        output.setLength(0);
-        builder.append("\"x\"");
-        JsonUtils.quoteAsString(builder, output);
-        assertEquals("\\\"x\\\"", output.toString());
-    }
-
-    // For [JACKSON-853]
-    @Test
-    public void testQuoteLongCharSequenceAsString() throws Exception {
-        final StringBuilder output = new StringBuilder();
-        final StringBuilder input = new StringBuilder();
-        final StringBuilder sb2 = new StringBuilder();
-        for (int i = 0; i < 1111; ++i) {
-            input.append('"');
-            sb2.append("\\\"");
-        }
-        final String exp = sb2.toString();
-        JsonUtils.quoteAsString(input, output);
-        assertEquals(2 * input.length(), output.length());
-        assertEquals(exp, output.toString());
-    }
-
-    // [JACKSON-884]
-    @Test
-    public void testCharSequenceWithCtrlChars() throws Exception {
-        final char[] input = new char[] {0, 1, 2, 3, 4};
-        final StringBuilder builder = new StringBuilder();
-        builder.append(input);
-        final StringBuilder output = new StringBuilder();
-        JsonUtils.quoteAsString(builder, output);
-        assertEquals("\\u0000\\u0001\\u0002\\u0003\\u0004", output.toString());
-    }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
index 09a6d89482..8964b642d9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
@@ -29,12 +29,12 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.core.config.ReliabilityStrategy;
 import org.apache.logging.log4j.core.filter.CompositeFilter;
 import org.apache.logging.log4j.kit.logger.AbstractLogger;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.Strings;
 import org.apache.logging.log4j.util.Supplier;
 import org.jspecify.annotations.NullMarked;
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index 3f1b4d9e5d..8e558f42d7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
@@ -87,6 +87,8 @@ public class LoggerContext extends AbstractLifeCycle
     public static final Key<LoggerContext> KEY = 
Key.forClass(LoggerContext.class);
 
     private final LoggerRegistry<Logger> loggerRegistry = new 
LoggerRegistry<>();
+    private final MessageFactory defaultMessageFactory;
+
     private final Collection<Consumer<Configuration>> 
configurationStartedListeners = new ArrayList<>();
     private final Collection<Consumer<Configuration>> 
configurationStoppedListeners = new ArrayList<>();
     private final Lazy<List<LoggerContextShutdownAware>> listeners = 
Lazy.relaxed(CopyOnWriteArrayList::new);
@@ -139,6 +141,7 @@ public class LoggerContext extends AbstractLifeCycle
         this.configLocation = configLocation;
         this.environment = 
instanceFactory.getInstance(PropertyEnvironment.class);
         this.configurationScheduler = 
instanceFactory.getInstance(ConfigurationScheduler.class);
+        this.defaultMessageFactory = 
instanceFactory.getInstance(MessageFactory.class);
 
         this.configuration = new DefaultConfiguration(this);
         this.nullConfiguration = new NullConfiguration(this);
@@ -180,10 +183,10 @@ public class LoggerContext extends AbstractLifeCycle
      * @param logger The logger to check
      * @param messageFactory The message factory to check.
      */
-    public static void checkMessageFactory(final ExtendedLogger logger, final 
MessageFactory messageFactory) {
+    private void checkMessageFactory(final ExtendedLogger logger, final 
MessageFactory messageFactory) {
         final String name = logger.getName();
         final MessageFactory loggerMessageFactory = logger.getMessageFactory();
-        final MessageFactory currentMessageFactory = 
LoggingSystem.getMessageFactory();
+        final MessageFactory currentMessageFactory = defaultMessageFactory;
         if (messageFactory != null && 
!loggerMessageFactory.equals(messageFactory)) {
             StatusLogger.getLogger()
                     .warn(
@@ -540,7 +543,7 @@ public class LoggerContext extends AbstractLifeCycle
      */
     @Override
     public Logger getLogger(final String name) {
-        return getLogger(name, null);
+        return getLogger(name, defaultMessageFactory);
     }
 
     /**
@@ -566,13 +569,14 @@ public class LoggerContext extends AbstractLifeCycle
      */
     @Override
     public Logger getLogger(final String name, final MessageFactory 
messageFactory) {
+        final MessageFactory actualMessageFactory = messageFactory != null ? 
messageFactory : defaultMessageFactory;
         // Note: This is the only method where we add entries to the 
'loggerRegistry' ivar.
-        Logger logger = loggerRegistry.getLogger(name, messageFactory);
+        Logger logger = loggerRegistry.getLogger(name, actualMessageFactory);
         if (logger != null) {
-            checkMessageFactory(logger, messageFactory);
+            checkMessageFactory(logger, actualMessageFactory);
             return logger;
         }
-        logger = newLogger(name, messageFactory);
+        logger = newLogger(name, actualMessageFactory);
         loggerRegistry.putIfAbsent(name, logger.getMessageFactory(), logger);
         return loggerRegistry.getLogger(name, logger.getMessageFactory());
     }
@@ -606,7 +610,7 @@ public class LoggerContext extends AbstractLifeCycle
      */
     @Override
     public boolean hasLogger(final String name) {
-        return loggerRegistry.hasLogger(name);
+        return loggerRegistry.hasLogger(name, defaultMessageFactory);
     }
 
     /**
@@ -910,13 +914,12 @@ public class LoggerContext extends AbstractLifeCycle
         return Logger.Builder.class;
     }
 
-    private Logger newLogger(final String name, final @Nullable MessageFactory 
messageFactory) {
-        final Logger.Builder builder =
-                
instanceFactory.getInstance(getLoggerBuilderClass()).setName(name);
-        if (messageFactory != null) {
-            builder.setMessageFactory(messageFactory);
-        }
-        return builder.build();
+    private Logger newLogger(final String name, final MessageFactory 
messageFactory) {
+        return instanceFactory
+                .getInstance(getLoggerBuilderClass())
+                .setName(name)
+                .setMessageFactory(messageFactory)
+                .build();
     }
 
     /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
index 933456f6b1..b0e063fb88 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Configuration.java
@@ -36,9 +36,9 @@ import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.core.util.WatchManager;
 import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * Interface that must be implemented to create a configuration.
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
index 2a21b56b22..f9b66888fb 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
@@ -28,6 +28,8 @@ import org.apache.logging.log4j.core.Logger;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.StructuredDataMessage;
 import org.apache.logging.log4j.plugins.Configurable;
@@ -35,8 +37,6 @@ import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginAttribute;
 import org.apache.logging.log4j.plugins.PluginElement;
 import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
 import org.apache.logging.log4j.util.PerformanceSensitive;
 import org.apache.logging.log4j.util.StringBuilders;
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 2f176ea286..31ff01bacb 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,12 @@
  */
 package org.apache.logging.log4j.core.impl;
 
+import java.util.Comparator;
 import java.util.Map;
+import java.util.Optional;
+import java.util.ServiceLoader;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
@@ -27,6 +31,7 @@ import 
org.apache.logging.log4j.core.config.DefaultConfigurationFactory;
 import org.apache.logging.log4j.core.config.URIConfigurationFactory;
 import org.apache.logging.log4j.core.config.composite.DefaultMergeStrategy;
 import org.apache.logging.log4j.core.config.composite.MergeStrategy;
+import org.apache.logging.log4j.core.impl.internal.ReusableMessageFactory;
 import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
 import org.apache.logging.log4j.core.lookup.Interpolator;
 import org.apache.logging.log4j.core.lookup.InterpolatorFactory;
@@ -40,6 +45,11 @@ import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
 import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
 import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerKeys;
+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.plugins.Named;
@@ -49,11 +59,10 @@ import 
org.apache.logging.log4j.plugins.condition.ConditionalOnMissingBinding;
 import org.apache.logging.log4j.plugins.di.ConfigurableInstanceFactory;
 import org.apache.logging.log4j.spi.CopyOnWrite;
 import org.apache.logging.log4j.spi.LoggerContextFactory;
-import org.apache.logging.log4j.spi.LoggingSystem;
 import org.apache.logging.log4j.spi.Provider;
 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
 
 /**
  * Provides instance binding defaults.
@@ -84,20 +93,37 @@ public class DefaultBundle {
 
     @SingletonFactory
     @ConditionalOnMissingBinding
-    public MessageFactory defaultMessageFactory() {
-        return LoggingSystem.getMessageFactory();
+    public MessageFactory defaultMessageFactory(final RecyclerFactory 
recyclerFactory) {
+        return new ReusableMessageFactory(recyclerFactory);
     }
 
     @SingletonFactory
     @ConditionalOnMissingBinding
     public FlowMessageFactory defaultFlowMessageFactory() {
-        return LoggingSystem.getFlowMessageFactory();
+        return new DefaultFlowMessageFactory();
     }
 
     @SingletonFactory
     @ConditionalOnMissingBinding
-    public RecyclerFactory defaultRecyclerFactory() {
-        return LoggingSystem.getRecyclerFactory();
+    public RecyclerFactoryProvider defaultRecyclerFactoryProvider(
+            final PropertyEnvironment environment,
+            final ClassLoader loader,
+            final @Named("StatusLogger") org.apache.logging.log4j.Logger 
statusLogger) {
+        final String factory =
+                environment.getProperty(RecyclerKeys.Recycler.class).factory();
+        final Stream<RecyclerFactoryProvider> providerStream = 
ServiceLoaderUtil.safeStream(
+                RecyclerFactoryProvider.class, 
ServiceLoader.load(RecyclerFactoryProvider.class, loader), statusLogger);
+        final Optional<RecyclerFactoryProvider> provider = factory != null
+                ? providerStream.filter(p -> 
factory.equals(p.getName())).findAny()
+                : 
providerStream.min(Comparator.comparing(RecyclerFactoryProvider::getOrder));
+        return provider.orElseGet(RecyclerFactoryProvider::getInstance);
+    }
+
+    @SingletonFactory
+    @ConditionalOnMissingBinding
+    public RecyclerFactory defaultRecyclerFactory(
+            final PropertyEnvironment environment, final 
RecyclerFactoryProvider provider) {
+        return provider.createForEnvironment(environment);
     }
 
     @SingletonFactory
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index fa1f47d5a3..ab96103fec 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -27,13 +27,13 @@ import org.apache.logging.log4j.core.time.Instant;
 import org.apache.logging.log4j.core.time.MutableInstant;
 import org.apache.logging.log4j.core.time.NanoClock;
 import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.kit.recycler.Recycler;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ParameterConsumer;
 import org.apache.logging.log4j.message.ParameterVisitable;
 import org.apache.logging.log4j.message.ReusableMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.TimestampMessage;
-import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.StringBuilders;
 import org.apache.logging.log4j.util.StringMap;
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 7a19db87b3..e70d9faed2 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
@@ -26,10 +26,10 @@ import org.apache.logging.log4j.core.ReusableLogEvent;
 import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.time.Clock;
 import org.apache.logging.log4j.core.time.NanoClock;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.plugins.Inject;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * Garbage-free LogEventFactory that recycles mutable {@link LogEvent} 
instances.
diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java
similarity index 67%
copy from 
log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
copy to 
log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java
index 6ec36d5308..e024b2d2e6 100644
--- 
a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/internal/ReusableMessageFactory.java
@@ -14,78 +14,52 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.message;
-
-import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+package org.apache.logging.log4j.core.impl.internal;
+
+import org.apache.logging.log4j.kit.message.RecyclingMessageFactory;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.ReusableObjectMessage;
+import org.apache.logging.log4j.message.ReusableParameterizedMessage;
+import org.apache.logging.log4j.message.ReusableSimpleMessage;
 import org.apache.logging.log4j.util.PerformanceSensitive;
 
 /**
- * Implementation of the {@link MessageFactory} interface that avoids 
allocating temporary objects where possible.
+ * Message factory that avoids allocating temporary objects where possible.
+ * <p>
+ *     Message instances are cached in a {@link Recycler} and reused when a 
new message is requested.
+ * </p>
  * 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 ReusableSimpleMessage
- * @see ReusableObjectMessage
- * @see ReusableParameterizedMessage
  * @see Recycler
- * @since 2.6
+ * @since 3.0.0
  */
 @PerformanceSensitive("allocation")
-public final class ReusableMessageFactory implements MessageFactory {
-
-    /**
-     * Instance of {@link ReusableMessageFactory}.
-     */
-    public static final ReusableMessageFactory INSTANCE = new 
ReusableMessageFactory();
+public final class ReusableMessageFactory implements RecyclingMessageFactory {
 
     private final Recycler<ReusableParameterizedMessage> 
parameterizedMessageRecycler;
     private final Recycler<ReusableSimpleMessage> simpleMessageRecycler;
     private final Recycler<ReusableObjectMessage> objectMessageRecycler;
 
-    /**
-     * Constructs a message factory using the default {@link RecyclerFactory}.
-     */
-    public ReusableMessageFactory() {
-        this(LoggingSystem.getRecyclerFactory());
-    }
-
     public ReusableMessageFactory(final RecyclerFactory recyclerFactory) {
-        super();
         parameterizedMessageRecycler =
                 recyclerFactory.create(ReusableParameterizedMessage::new, 
ReusableParameterizedMessage::clear);
         simpleMessageRecycler = 
recyclerFactory.create(ReusableSimpleMessage::new, 
ReusableSimpleMessage::clear);
         objectMessageRecycler = 
recyclerFactory.create(ReusableObjectMessage::new, 
ReusableObjectMessage::clear);
     }
 
-    /**
-     * Invokes {@link ReusableMessage#clear()} when possible.
-     * This flag is used internally to verify that a reusable message is no 
longer in use and
-     * can be reused.
-     * @param message the message to make available again
-     * @since 2.7
-     */
-    @SuppressWarnings("removal")
-    public static void release(final Message message) { // LOG4J2-1583
-        if (message instanceof ReusableMessage) {
-            ((ReusableMessage) message).clear();
-        } else if (message instanceof Clearable) {
-            ((Clearable) message).clear();
-        }
-    }
-
     @Override
     public void recycle(final Message message) {
-        if (message instanceof ReusableMessage) {
-            ((ReusableMessage) message).clear();
-        }
         // related to LOG4J2-1583 and nested log messages clobbering each 
other. recycle messages today!
-        if (message instanceof ReusableParameterizedMessage) {
-            
parameterizedMessageRecycler.release((ReusableParameterizedMessage) message);
-        } else if (message instanceof ReusableObjectMessage) {
-            objectMessageRecycler.release((ReusableObjectMessage) message);
-        } else if (message instanceof ReusableSimpleMessage) {
-            simpleMessageRecycler.release((ReusableSimpleMessage) message);
+        if (message instanceof final ReusableParameterizedMessage reusable) {
+            reusable.clear();
+            parameterizedMessageRecycler.release(reusable);
+        } else if (message instanceof final ReusableObjectMessage reusable) {
+            reusable.clear();
+            objectMessageRecycler.release(reusable);
+        } else if (message instanceof final ReusableSimpleMessage reusable) {
+            reusable.clear();
+            simpleMessageRecycler.release(reusable);
         }
     }
 
@@ -96,15 +70,6 @@ public final class ReusableMessageFactory implements 
MessageFactory {
         return result;
     }
 
-    /**
-     * Creates {@link ReusableParameterizedMessage} 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 parameterizedMessageRecycler.acquire().set(message, params);
@@ -207,14 +172,6 @@ public final class ReusableMessageFactory implements 
MessageFactory {
         return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4, p5, p6, p7, p8, p9);
     }
 
-    /**
-     * Creates {@link ReusableSimpleMessage} instances.
-     *
-     * @param message The message String.
-     * @return The Message.
-     *
-     * @see MessageFactory#newMessage(String)
-     */
     @Override
     public Message newMessage(final String message) {
         final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
@@ -222,14 +179,6 @@ public final class ReusableMessageFactory implements 
MessageFactory {
         return result;
     }
 
-    /**
-     * Creates {@link ReusableObjectMessage} instances.
-     *
-     * @param message The message Object.
-     * @return The Message.
-     *
-     * @see MessageFactory#newMessage(Object)
-     */
     @Override
     public Message newMessage(final Object message) {
         final ReusableObjectMessage result = objectMessageRecycler.acquire();
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
index ed6953066c..0bfb52df1c 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
@@ -24,9 +24,9 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.logging.log4j.core.impl.CoreKeys;
 import org.apache.logging.log4j.core.impl.LogEventFactory;
 import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.plugins.PluginElement;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.StringBuilders;
 
 /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index c7c238078b..cc41b925c6 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -34,12 +34,12 @@ import 
org.apache.logging.log4j.core.pattern.PatternFormatter;
 import org.apache.logging.log4j.core.pattern.PatternParser;
 import org.apache.logging.log4j.core.pattern.RegexReplacement;
 import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
 import org.apache.logging.log4j.plugins.Configurable;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.plugins.PluginElement;
 import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.Strings;
 
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
index 360dd56e06..dc6f160751 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/StringBuilderEncoder.java
@@ -23,7 +23,7 @@ import java.nio.charset.CharsetEncoder;
 import java.nio.charset.CodingErrorAction;
 import java.util.Objects;
 import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 
 /**
  * {@link Encoder} for {@link StringBuilder}s.
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 d923c34f7c..09cc6c7ddd 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
@@ -28,9 +28,9 @@ import org.apache.logging.log4j.core.time.MutableInstant;
 import org.apache.logging.log4j.core.time.internal.format.FastDateFormat;
 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.kit.recycler.Recycler;
 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;
 
 /**
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
deleted file mode 100644
index a60d6e4c9c..0000000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/JsonUtils.java
+++ /dev/null
@@ -1,117 +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.util;
-
-import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.util.Lazy;
-
-/**
- * This class is borrowed from <a 
href="https://github.com/FasterXML/jackson-core";>Jackson</a>.
- */
-public final class JsonUtils {
-
-    private static final char[] HC = "0123456789ABCDEF".toCharArray();
-
-    /**
-     * Read-only encoding table for first 128 Unicode code points (single-byte 
UTF-8 characters).
-     * Value of 0 means "no escaping"; other positive values that value is 
character
-     * to use after backslash; and negative values that generic (backslash - u)
-     * escaping is to be used.
-     */
-    private static final Lazy<int[]> ESC_CODES = Lazy.pure(() -> {
-        final int[] table = new int[128];
-        // Control chars need generic escape sequence
-        for (int i = 0; i < 32; ++i) {
-            // 04-Mar-2011, tatu: Used to use "-(i + 1)", replaced with 
constant
-            table[i] = -1;
-        }
-        /* Others (and some within that range too) have explicit shorter
-         * sequences
-         */
-        table['"'] = '"';
-        table['\\'] = '\\';
-        // Escaping of slash is optional, so let's not add it
-        table[0x08] = 'b';
-        table[0x09] = 't';
-        table[0x0C] = 'f';
-        table[0x0A] = 'n';
-        table[0x0D] = 'r';
-        return table;
-    });
-
-    /**
-     * Temporary buffer used for composing quote/escape sequences
-     */
-    private static final Recycler<char[]> qbufRecycler = 
LoggingSystem.getRecyclerFactory()
-            .create(() -> {
-                char[] qbuf = new char[6];
-                qbuf[0] = '\\';
-                qbuf[2] = '0';
-                qbuf[3] = '0';
-                return qbuf;
-            });
-
-    /**
-     * Quote text contents using JSON standard quoting, and append results to 
a supplied {@link StringBuilder}.
-     */
-    public static void quoteAsString(final CharSequence input, final 
StringBuilder output) {
-        final char[] qbuf = qbufRecycler.acquire();
-        try {
-            final int[] escCodes = ESC_CODES.get();
-            final int escCodeCount = escCodes.length;
-            int inPtr = 0;
-            final int inputLen = input.length();
-
-            outer:
-            while (inPtr < inputLen) {
-                tight_loop:
-                while (true) {
-                    final char c = input.charAt(inPtr);
-                    if (c < escCodeCount && escCodes[c] != 0) {
-                        break tight_loop;
-                    }
-                    output.append(c);
-                    if (++inPtr >= inputLen) {
-                        break outer;
-                    }
-                }
-                // something to escape; 2 or 6-char variant?
-                final char d = input.charAt(inPtr++);
-                final int escCode = escCodes[d];
-                final int length = (escCode < 0) ? _appendNumeric(d, qbuf) : 
_appendNamed(escCode, qbuf);
-
-                output.append(qbuf, 0, length);
-            }
-        } finally {
-            qbufRecycler.release(qbuf);
-        }
-    }
-
-    private static int _appendNumeric(final int value, final char[] qbuf) {
-        qbuf[1] = 'u';
-        // We know it's a control char, so only the last 2 chars are non-0
-        qbuf[4] = HC[value >> 4];
-        qbuf[5] = HC[value & 0xF];
-        return 6;
-    }
-
-    private static int _appendNamed(final int esc, final char[] qbuf) {
-        qbuf[1] = (char) esc;
-        return 2;
-    }
-}
diff --git 
a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
 
b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
index 435dfde08d..9c830453bf 100644
--- 
a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
+++ 
b/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
@@ -17,17 +17,18 @@
 package org.apache.logging.log4j.jctools;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+import static org.apache.logging.log4j.kit.recycler.Recycler.DEFAULT_CAPACITY;
 
 import aQute.bnd.annotation.spi.ServiceProvider;
 import java.util.Queue;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.util.PropertyEnvironment;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerKeys;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
 import org.jctools.queues.MpmcArrayQueue;
 
 /**
@@ -51,11 +52,12 @@ public final class JCToolsRecyclerFactoryProvider 
implements RecyclerFactoryProv
     @Override
     public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
         requireNonNull(environment, "environment");
-        final int capacity = 
environment.getIntegerProperty("Recycler.capacity", DEFAULT_CAPACITY);
-        if (capacity < 1) {
+        final Integer capacity =
+                
environment.getProperty(RecyclerKeys.Recycler.class).capacity();
+        if (capacity != null && capacity < 1) {
             throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
         }
-        return new JCToolsMpmcRecyclerFactory(capacity);
+        return new JCToolsMpmcRecyclerFactory(capacity != null ? capacity : 
DEFAULT_CAPACITY);
     }
 
     private static final class JCToolsMpmcRecyclerFactory implements 
RecyclerFactory {
diff --git 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
 
b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
index 51e9d9e272..e5d121db0f 100644
--- 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
+++ 
b/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
@@ -20,15 +20,21 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.Comparator;
 import java.util.List;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
+import java.util.ServiceLoader;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
 import org.junit.jupiter.api.Test;
 
 class JCToolsRecyclerFactoryProviderTest {
 
     @Test
     void verify_is_the_first() {
-        final List<Class<?>> providerClasses = 
RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
+        final List<Class<?>> providerClasses = ServiceLoaderUtil.safeStream(
+                        RecyclerFactoryProvider.class,
+                        ServiceLoader.load(
+                                RecyclerFactoryProvider.class, 
getClass().getClassLoader()),
+                        StatusLogger.getLogger())
                 
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
                 .<Class<?>>map(RecyclerFactoryProvider::getClass)
                 .toList();
diff --git 
a/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
 
b/log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider
similarity index 100%
rename from 
log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider
rename to 
log4j-jctools/src/test/resources/META-INF/services/org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
index 45a2a9312c..3fbcfbd2bd 100644
--- 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/AbstractLogger.java
@@ -23,14 +23,17 @@ import org.apache.logging.log4j.LoggingException;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.MarkerManager;
 import org.apache.logging.log4j.kit.logger.internal.DefaultLogBuilder;
+import org.apache.logging.log4j.kit.message.RecyclingMessageFactory;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.message.EntryMessage;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.message.MessageFactory2;
 import org.apache.logging.log4j.message.StringFormattedMessage;
 import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
+import org.apache.logging.log4j.spi.MessageFactory2Adapter;
 import org.apache.logging.log4j.util.LambdaUtil;
 import org.apache.logging.log4j.util.MessageSupplier;
 import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -107,7 +110,7 @@ public abstract class AbstractLogger implements 
ExtendedLogger {
     private static final ThreadLocal<int[]> recursionDepthHolder = new 
ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031
 
     private final String name;
-    private final MessageFactory messageFactory;
+    private final MessageFactory2 messageFactory;
     private final FlowMessageFactory flowMessageFactory;
     private final Recycler<DefaultLogBuilder> recycler;
     private final Logger statusLogger;
@@ -125,7 +128,9 @@ public abstract class AbstractLogger implements 
ExtendedLogger {
             final RecyclerFactory recyclerFactory,
             final Logger statusLogger) {
         this.name = name;
-        this.messageFactory = messageFactory;
+        this.messageFactory = messageFactory instanceof final MessageFactory2 
messageFactory2
+                ? messageFactory2
+                : new MessageFactory2Adapter(messageFactory);
         this.flowMessageFactory = flowMessageFactory;
         this.recycler = recyclerFactory.create(DefaultLogBuilder::new);
         this.statusLogger = statusLogger;
@@ -242,7 +247,9 @@ public abstract class AbstractLogger implements 
ExtendedLogger {
     // NOTE: This is a hot method. Current implementation compiles to 33 bytes 
of byte code.
     // This is within the 35 byte MaxInlineSize threshold. Modify with care!
     private void recycle(final @Nullable Message message) {
-        messageFactory.recycle(message);
+        if (messageFactory instanceof final RecyclingMessageFactory 
recyclingMessageFactory) {
+            recyclingMessageFactory.recycle(message);
+        }
     }
 
     @PerformanceSensitive
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
index 47052fd14d..21a1cf92b3 100644
--- 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/logger/internal/DefaultLogBuilder.java
@@ -21,11 +21,11 @@ import org.apache.logging.log4j.BridgeAware;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogBuilder;
 import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerAware;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerAware;
 import org.apache.logging.log4j.util.LambdaUtil;
 import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.Strings;
diff --git 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java
similarity index 50%
copy from 
log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
copy to 
log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java
index 51e9d9e272..74c33b6f6d 100644
--- 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/RecyclingMessageFactory.java
@@ -14,24 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.jctools;
+package org.apache.logging.log4j.kit.message;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory2;
 
-import java.util.Comparator;
-import java.util.List;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
-import org.junit.jupiter.api.Test;
-
-class JCToolsRecyclerFactoryProviderTest {
+/**
+ * A message factory backed by a {@link Recycler}.
+ * <p>
+ *     Messages acquired from this factory <strong>must</strong> be released 
using the {@link #recycle} method.
+ * </p>
+ */
+public interface RecyclingMessageFactory extends MessageFactory2 {
 
-    @Test
-    void verify_is_the_first() {
-        final List<Class<?>> providerClasses = 
RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
-                
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
-                .<Class<?>>map(RecyclerFactoryProvider::getClass)
-                .toList();
-        
assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class);
-    }
+    /**
+     * Recycles a message back for potential reuse or cleanup.
+     * <p>
+     *
+     * </p>
+     * @see Recycler
+     */
+    void recycle(Message message);
 }
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/package-info.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/package-info.java
new file mode 100644
index 0000000000..a8f822b176
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.kit.message;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.java 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.java
new file mode 100644
index 0000000000..99384388bb
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/Recycler.java
@@ -0,0 +1,48 @@
+/*
+ * 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.kit.recycler;
+
+/**
+ * Contract for recycling strategies.
+ * This is the primary building block for logging components striving for 
garbage-free operation.
+ *
+ * @param <V> the recyclable type
+ * @since 3.0.0
+ */
+public interface Recycler<V> {
+
+    /**
+     * The default recycler capacity: {@code max(2C+1, 8)}, {@code C} denoting 
the number of available processors
+     */
+    int DEFAULT_CAPACITY = Math.max(2 * 
Runtime.getRuntime().availableProcessors() + 1, 8);
+
+    /**
+     * Acquires an instance of V. This may either be a fresh instance of V or 
a recycled instance of V.
+     * Recycled instances will be modified by their cleanup function before 
being returned.
+     *
+     * @return an instance of V to be used
+     */
+    V acquire();
+
+    /**
+     * Releases an instance of V. This allows the instance to be recycled and 
later reacquired for new
+     * purposes.
+     *
+     * @param value an instance of V no longer being used
+     */
+    void release(V value);
+}
diff --git 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java
similarity index 50%
copy from 
log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
copy to 
log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java
index 51e9d9e272..aaaa6ce17a 100644
--- 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerAware.java
@@ -14,24 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.jctools;
+package org.apache.logging.log4j.kit.recycler;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.Comparator;
-import java.util.List;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
-import org.junit.jupiter.api.Test;
-
-class JCToolsRecyclerFactoryProviderTest {
+/**
+ * Interface implemented by classes that need to interact with the {@link 
Recycler} that created them.
+ *
+ * @since 3.0.0
+ */
+@FunctionalInterface
+public interface RecyclerAware<V> {
 
-    @Test
-    void verify_is_the_first() {
-        final List<Class<?>> providerClasses = 
RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
-                
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
-                .<Class<?>>map(RecyclerFactoryProvider::getClass)
-                .toList();
-        
assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class);
-    }
+    void setRecycler(Recycler<V> recycler);
 }
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactory.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactory.java
new file mode 100644
index 0000000000..e209ef6497
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactory.java
@@ -0,0 +1,58 @@
+/*
+ * 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.kit.recycler;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Contract for {@link Recycler} factories.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactory {
+
+    /**
+     * Creates a new recycler using the given supplier function for initial 
instances.
+     *
+     * @param supplier a function to provide initial instances
+     * @param <V> the recyclable type
+     * @return a new recycler
+     */
+    default <V> Recycler<V> create(final Supplier<V> supplier) {
+        return create(supplier, ignored -> {});
+    }
+
+    /**
+     * Creates a new recycler using the given supplier and cleaner functions.
+     * <p>
+     * The provided supplier needs to make sure that generated instances are 
always clean.
+     * </p>
+     * <p>
+     * Recycled instances are always guaranteed to be clean.
+     * The cleaning of an instance can take place either just before 
acquisition or prior to admitting it back into the reusable instances pool.
+     * The moment when the cleaning will be carried out is implementation 
dependent.
+     * Though a released instance should ideally be cleaned immediately to 
avoid keeping references to unused objects.
+     * </p>
+     *
+     * @param supplier a function to provide initial (and clean!) instances
+     * @param cleaner function to reset an instance before reuse
+     * @param <V> the recyclable type
+     * @return a new recycler
+     */
+    <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner);
+}
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java
new file mode 100644
index 0000000000..26ebd5900e
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java
@@ -0,0 +1,64 @@
+/*
+ * 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.kit.recycler;
+
+import edu.umd.cs.findbugs.annotations.Nullable;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import 
org.apache.logging.log4j.kit.recycler.internal.DummyRecyclerFactoryProvider;
+
+/**
+ * Contract for providing {@link RecyclerFactory} instances.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactoryProvider {
+
+    static RecyclerFactoryProvider getInstance() {
+        return DummyRecyclerFactoryProvider.INSTANCE;
+    }
+
+    /**
+     * Denotes the value to be used while sorting recycler factory providers 
to determine the precedence order.
+     * Values will be sorted naturally, that is, lower values will imply 
higher precedence.
+     *
+     * @return the value to be used while sorting
+     */
+    default int getOrder() {
+        return 0;
+    }
+
+    /**
+     * The name of this recycler factory provider.
+     * Recycler factory providers are required to have unique names.
+     *
+     * @return the name of this recycler factory provider
+     */
+    String getName();
+
+    /**
+     * Creates a recycler factory for the provided environment.
+     * <p>
+     * The return value can be null indicating that the recycler factory is 
not available for the provided environment.
+     * For instance, the provider of a {@link ThreadLocal}-based recycler 
factory can return null if the environment is of a web application.
+     * </p>
+     *
+     * @param environment an environment
+     * @return either a recycler factory instance, or null, if the associated 
recycler factory is not available for the given environment
+     */
+    @Nullable
+    RecyclerFactory createForEnvironment(PropertyEnvironment environment);
+}
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java
new file mode 100644
index 0000000000..973bed589b
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueue.java
@@ -0,0 +1,100 @@
+/*
+ * 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.kit.recycler.internal;
+
+import java.util.AbstractQueue;
+import java.util.Iterator;
+import java.util.stream.IntStream;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.apache.logging.log4j.util.InternalApi;
+
+/**
+ * An array-backed, fixed-length, not-thread-safe {@link java.util.Queue} 
implementation.
+ *
+ * @param <E> the element type
+ */
+@InternalApi
+@NotThreadSafe
+final class ArrayQueue<E> extends AbstractQueue<E> {
+
+    private final E[] buffer;
+
+    private int head;
+
+    private int tail;
+
+    private int size;
+
+    @SuppressWarnings("unchecked")
+    ArrayQueue(final int capacity) {
+        if (capacity < 1) {
+            throw new IllegalArgumentException("invalid capacity: " + 
capacity);
+        }
+        buffer = (E[]) new Object[capacity];
+        head = 0;
+        tail = -1;
+        size = 0;
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        int[] i = {head};
+        return IntStream.range(0, size)
+                .mapToObj(ignored -> {
+                    final E item = buffer[i[0]];
+                    i[0] = (i[0] + 1) % buffer.length;
+                    return item;
+                })
+                .iterator();
+    }
+
+    @Override
+    public boolean offer(final E item) {
+        if (size == buffer.length) {
+            return false;
+        }
+        tail = (tail + 1) % buffer.length;
+        buffer[tail] = item;
+        size++;
+        return true;
+    }
+
+    @Override
+    public E poll() {
+        if (isEmpty()) {
+            return null;
+        }
+        final E item = buffer[head];
+        buffer[head] = null; // Clear refs for GC
+        head = (head + 1) % buffer.length;
+        size--;
+        return item;
+    }
+
+    @Override
+    public E peek() {
+        if (isEmpty()) {
+            return null;
+        }
+        return buffer[head];
+    }
+
+    @Override
+    public int size() {
+        return size;
+    }
+}
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..8a58bbcac0
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java
@@ -0,0 +1,83 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static java.util.Objects.requireNonNull;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler does not recycle 
anything; all instances are freshly created.
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class DummyRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+
+    public static final RecyclerFactoryProvider INSTANCE = new 
DummyRecyclerFactoryProvider();
+
+    @Override
+    public int getOrder() {
+        return 900;
+    }
+
+    @Override
+    public String getName() {
+        return "dummy";
+    }
+
+    @Override
+    public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
+        return DummyRecyclerFactory.INSTANCE;
+    }
+
+    // Visible for testing
+    static final class DummyRecyclerFactory implements RecyclerFactory {
+
+        private static final DummyRecyclerFactory INSTANCE = new 
DummyRecyclerFactory();
+
+        private DummyRecyclerFactory() {}
+
+        @Override
+        public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
+            requireNonNull(supplier, "supplier");
+            return new DummyRecycler<>(supplier);
+        }
+
+        private static final class DummyRecycler<V> extends 
AbstractRecycler<V> {
+
+            private DummyRecycler(final Supplier<V> supplier) {
+                super(supplier);
+            }
+
+            @Override
+            public V acquire() {
+                return createInstance();
+            }
+
+            @Override
+            public void release(final V value) {}
+        }
+    }
+}
diff --git 
a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java
similarity index 60%
copy from 
log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
copy to 
log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java
index 435dfde08d..fab04ed718 100644
--- 
a/log4j-jctools/src/main/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProvider.java
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/QueueingRecyclerFactoryProvider.java
@@ -14,38 +14,36 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.jctools;
+package org.apache.logging.log4j.kit.recycler.internal;
 
 import static java.util.Objects.requireNonNull;
-import static org.apache.logging.log4j.spi.recycler.Recycler.DEFAULT_CAPACITY;
+import static org.apache.logging.log4j.kit.recycler.Recycler.DEFAULT_CAPACITY;
 
 import aQute.bnd.annotation.spi.ServiceProvider;
 import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
-import org.apache.logging.log4j.spi.recycler.AbstractRecycler;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.util.PropertyEnvironment;
-import org.jctools.queues.MpmcArrayQueue;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
 
 /**
- * A {@link Recycler} factory provider implementation based on <a 
href="https://jctools.github.io/JCTools/";>JCTools</a>.
- *
- * @since 3.0.0
+ * A {@link Recycler} factory provider such that the recycler pools objects in 
a fixed-size queue.
  */
 @ServiceProvider(RecyclerFactoryProvider.class)
-public final class JCToolsRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+public final class QueueingRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
 
     @Override
     public int getOrder() {
-        return 600;
+        return 800;
     }
 
     @Override
     public String getName() {
-        return "jctools-mpmc";
+        return "queue";
     }
 
     @Override
@@ -55,32 +53,36 @@ public final class JCToolsRecyclerFactoryProvider 
implements RecyclerFactoryProv
         if (capacity < 1) {
             throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
         }
-        return new JCToolsMpmcRecyclerFactory(capacity);
+        return new QueueingRecyclerFactory(capacity);
     }
 
-    private static final class JCToolsMpmcRecyclerFactory implements 
RecyclerFactory {
+    // Visible for testing
+    static final class QueueingRecyclerFactory implements RecyclerFactory {
 
-        private final int capacity;
+        // Visible for testing
+        final int capacity;
 
-        private JCToolsMpmcRecyclerFactory(final int capacity) {
+        private QueueingRecyclerFactory(int capacity) {
             this.capacity = capacity;
         }
 
         @Override
-        public <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> 
cleaner) {
+        public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
             requireNonNull(supplier, "supplier");
             requireNonNull(cleaner, "cleaner");
-            final MpmcArrayQueue<V> queue = new MpmcArrayQueue<>(capacity);
-            return new JCToolsMpmcRecycler<>(supplier, cleaner, queue);
+            final Queue<V> queue = new ArrayBlockingQueue<>(capacity);
+            return new QueueingRecycler<>(supplier, cleaner, queue);
         }
 
-        private static final class JCToolsMpmcRecycler<V> extends 
AbstractRecycler<V> {
+        // Visible for testing
+        static final class QueueingRecycler<V> extends AbstractRecycler<V> {
 
             private final Consumer<V> cleaner;
 
-            private final Queue<V> queue;
+            // Visible for testing
+            final Queue<V> queue;
 
-            private JCToolsMpmcRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final Queue<V> queue) {
+            private QueueingRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final Queue<V> queue) {
                 super(supplier);
                 this.cleaner = cleaner;
                 this.queue = queue;
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java
new file mode 100644
index 0000000000..2fc3d681bc
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProvider.java
@@ -0,0 +1,123 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static java.util.Objects.requireNonNull;
+import static org.apache.logging.log4j.kit.recycler.Recycler.DEFAULT_CAPACITY;
+import static org.apache.logging.log4j.util.LoaderUtil.isClassAvailable;
+
+import aQute.bnd.annotation.spi.ServiceProvider;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerKeys;
+import org.apache.logging.log4j.kit.recycler.support.AbstractRecycler;
+
+/**
+ * A {@link Recycler} factory provider such that the recycler pools objects in 
a fixed-size queue stored in a {@link ThreadLocal}.
+ * <p>
+ * This strategy may not be appropriate in workloads where units of work are 
independent of operating system threads such as reactive streams, coroutines, 
or virtual threads.
+ * For such use cases, see {@link QueueingRecyclerFactoryProvider}.
+ * </p>
+ *
+ * @since 3.0.0
+ */
+@ServiceProvider(RecyclerFactoryProvider.class)
+public final class ThreadLocalRecyclerFactoryProvider implements 
RecyclerFactoryProvider {
+
+    private static final boolean SERVLET_API_PRESENT =
+            isClassAvailable("javax.servlet.Servlet") || 
isClassAvailable("jakarta.servlet.Servlet");
+
+    @Override
+    public int getOrder() {
+        return SERVLET_API_PRESENT ? Integer.MAX_VALUE : 700;
+    }
+
+    @Override
+    public String getName() {
+        return "threadLocal";
+    }
+
+    @Override
+    public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
+        requireNonNull(environment, "environment");
+        final Integer capacity =
+                
environment.getProperty(RecyclerKeys.Recycler.class).capacity();
+        if (capacity != null && capacity < 1) {
+            throw new IllegalArgumentException("was expecting a `capacity` 
greater than 1, found: " + capacity);
+        }
+        return new ThreadLocalRecyclerFactory(capacity != null ? capacity : 
DEFAULT_CAPACITY);
+    }
+
+    // Visible for testing
+    static final class ThreadLocalRecyclerFactory implements RecyclerFactory {
+
+        /**
+         * Maximum number of objects retained per thread.
+         * <p>
+         * This allows to acquire objects in recursive method calls and 
maintain minimal overhead in the scenarios where the active instance count goes 
far beyond this for a brief moment.
+         * </p>
+         */
+        // Visible for testing
+        final int capacity;
+
+        private ThreadLocalRecyclerFactory(int capacity) {
+            this.capacity = capacity;
+        }
+
+        @Override
+        public <V> Recycler<V> create(final Supplier<V> supplier, final 
Consumer<V> cleaner) {
+            requireNonNull(supplier, "supplier");
+            requireNonNull(cleaner, "cleaner");
+            return new ThreadLocalRecycler<>(supplier, cleaner, capacity);
+        }
+
+        // Visible for testing
+        static final class ThreadLocalRecycler<V> extends AbstractRecycler<V> {
+
+            private final Consumer<V> cleaner;
+
+            // Visible for testing
+            final ThreadLocal<Queue<V>> queueRef;
+
+            private ThreadLocalRecycler(final Supplier<V> supplier, final 
Consumer<V> cleaner, final int capacity) {
+                super(supplier);
+                this.queueRef = ThreadLocal.withInitial(() -> new 
ArrayQueue<>(capacity));
+                this.cleaner = cleaner;
+            }
+
+            @Override
+            public V acquire() {
+                final Queue<V> queue = queueRef.get();
+                final V value = queue.poll();
+                return value != null ? value : createInstance();
+            }
+
+            @Override
+            public void release(final V value) {
+                requireNonNull(value, "value");
+                cleaner.accept(value);
+                final Queue<V> queue = queueRef.get();
+                queue.offer(value);
+            }
+        }
+    }
+}
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/package-info.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/package-info.java
new file mode 100644
index 0000000000..8d28b85cb9
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+/**
+ * Internal interfaces and classes to be used by authors of logging 
implementations or for internal use by
+ * API classes.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.kit.recycler;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/AbstractRecycler.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/AbstractRecycler.java
new file mode 100644
index 0000000000..764d7ec8d7
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/AbstractRecycler.java
@@ -0,0 +1,48 @@
+/*
+ * 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.kit.recycler.support;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.function.Supplier;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerAware;
+
+/**
+ * Abstract implementation of {@link Recycler} that properly handles {@link 
RecyclerAware} objects
+ *
+ * @param <V> The type of recycled object.
+ * @since 3.0.0
+ */
+public abstract class AbstractRecycler<V> implements Recycler<V> {
+
+    private final Supplier<V> supplier;
+
+    protected AbstractRecycler(final Supplier<V> supplier) {
+        this.supplier = requireNonNull(supplier, "supplier");
+    }
+
+    protected final V createInstance() {
+        final V instance = supplier.get();
+        if (instance instanceof RecyclerAware) {
+            @SuppressWarnings("unchecked")
+            final RecyclerAware<V> recyclerAware = (RecyclerAware<V>) instance;
+            recyclerAware.setRecycler(this);
+        }
+        return instance;
+    }
+}
diff --git 
a/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/package-info.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/package-info.java
new file mode 100644
index 0000000000..215ceeb792
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/support/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ * Support classes for the creation of recyclers.
+ */
+@Export
+@Version("3.0.0")
+package org.apache.logging.log4j.kit.recycler.support;
+
+import org.osgi.annotation.bundle.Export;
+import org.osgi.annotation.versioning.Version;
diff --git 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java
similarity index 51%
copy from 
log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
copy to 
log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java
index 51e9d9e272..7c1c63be58 100644
--- 
a/log4j-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/TestPropertyEnvironment.java
@@ -14,24 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.logging.log4j.jctools;
+package org.apache.logging.log4j.kit.env;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import java.util.Map;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.kit.env.support.BasicPropertyEnvironment;
+import org.apache.logging.log4j.status.StatusLogger;
 
-import java.util.Comparator;
-import java.util.List;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryProvider;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactoryRegistry;
-import org.junit.jupiter.api.Test;
+public class TestPropertyEnvironment extends BasicPropertyEnvironment {
 
-class JCToolsRecyclerFactoryProviderTest {
+    private final Map<String, String> props;
 
-    @Test
-    void verify_is_the_first() {
-        final List<Class<?>> providerClasses = 
RecyclerFactoryRegistry.getRecyclerFactoryProviders().stream()
-                
.sorted(Comparator.comparing(RecyclerFactoryProvider::getOrder))
-                .<Class<?>>map(RecyclerFactoryProvider::getClass)
-                .toList();
-        
assertThat(providerClasses).startsWith(JCToolsRecyclerFactoryProvider.class);
+    public TestPropertyEnvironment(final Map<String, String> props) {
+        this(props, StatusLogger.getLogger());
+    }
+
+    public TestPropertyEnvironment(final Map<String, String> props, final 
Logger logger) {
+        super(logger);
+        this.props = props;
+    }
+
+    @Override
+    public String getStringProperty(final String name) {
+        return props.get(name);
     }
 }
diff --git 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
index 0739532e18..72b228ed21 100644
--- 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/support/BasicPropertyEnvironmentTest.java
@@ -28,12 +28,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.stream.Stream;
 import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.kit.env.Log4jProperty;
 import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.env.TestPropertyEnvironment;
 import org.apache.logging.log4j.kit.logger.TestListLogger;
 import org.apache.logging.log4j.spi.StandardLevel;
-import org.apache.logging.log4j.status.StatusLogger;
 import org.assertj.core.api.Assertions;
 import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.Test;
@@ -222,23 +221,4 @@ class BasicPropertyEnvironmentTest {
         assertThat(actual).isEqualTo(expected);
         assertThat(logger.getMessages()).isEmpty();
     }
-
-    private static class TestPropertyEnvironment extends 
BasicPropertyEnvironment {
-
-        private final Map<String, String> props;
-
-        public TestPropertyEnvironment(final Map<String, String> props) {
-            this(props, StatusLogger.getLogger());
-        }
-
-        public TestPropertyEnvironment(final Map<String, String> props, final 
Logger logger) {
-            super(logger);
-            this.props = props;
-        }
-
-        @Override
-        public String getStringProperty(final String name) {
-            return props.get(name);
-        }
-    }
 }
diff --git 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
index acf6f3689d..9595c9bfa2 100644
--- 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/logger/TestListLogger.java
@@ -21,13 +21,13 @@ import java.util.Collections;
 import java.util.List;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.internal.recycler.DummyRecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import 
org.apache.logging.log4j.kit.recycler.internal.DummyRecyclerFactoryProvider;
 import org.apache.logging.log4j.message.DefaultFlowMessageFactory;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.ParameterizedNoReferenceMessageFactory;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.jspecify.annotations.NullMarked;
 import org.jspecify.annotations.Nullable;
diff --git 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueueTest.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueueTest.java
new file mode 100644
index 0000000000..40e6ac557f
--- /dev/null
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ArrayQueueTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ArrayBlockingQueue;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class ArrayQueueTest {
+
+    @ParameterizedTest
+    @ValueSource(ints = {-1, 0})
+    void invalid_capacity_should_not_be_allowed(final int invalidCapacity) {
+        assertThatThrownBy(() -> new ArrayQueue<>(invalidCapacity))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessage("invalid capacity: " + invalidCapacity);
+    }
+
+    @Test
+    void should_work_with_capacity_1() {
+
+        // Verify initials
+        final Queue<String> queue = new ArrayQueue<>(1);
+        assertThat(queue.size()).isEqualTo(0);
+        assertThat(queue.peek()).isNull();
+        assertThat(queue.poll()).isNull();
+        assertThat(queue).isEmpty();
+
+        // Verify enqueue & deque
+        assertThat(queue.offer("foo")).isTrue();
+        assertThat(queue.offer("bar")).isFalse();
+        assertThat(queue.size()).isEqualTo(1);
+        assertThat(queue).containsOnly("foo");
+        assertThat(queue.peek()).isEqualTo("foo");
+        assertThat(queue.poll()).isEqualTo("foo");
+
+        // Verify final state
+        assertThat(queue.size()).isEqualTo(0);
+        assertThat(queue.peek()).isNull();
+        assertThat(queue.poll()).isNull();
+        assertThat(queue).isEmpty();
+    }
+
+    @ParameterizedTest
+    @CsvSource({
+        "1,0.3", "1,0.5", "1,0.8", "2,0.3", "2,0.5", "2,0.8", "3,0.3", 
"3,0.5", "3,0.8", "4,0.3", "4,0.5", "4,0.8"
+    })
+    void ops_should_match_with_std_lib(final int capacity, final double 
pollRatio) {
+
+        // Set the stage
+        final Random random = new Random(0);
+        final int opCount = random.nextInt(100);
+        final Queue<String> queueRef = new ArrayBlockingQueue<>(capacity);
+        final Queue<String> queueTarget = new ArrayQueue<>(capacity);
+
+        for (int opIndex = 0; opIndex < opCount; opIndex++) {
+
+            // Verify entry
+            assertThat(queueTarget.size()).isEqualTo(queueRef.size());
+            assertThat(queueTarget.peek()).isEqualTo(queueRef.peek());
+            assertThat(queueTarget).containsExactlyElementsOf(queueRef);
+
+            // Is this a `poll()`?
+            if (pollRatio >= random.nextDouble()) {
+                assertThat(queueTarget.poll()).isEqualTo(queueRef.poll());
+            }
+
+            // Then this is an `offer()`
+            else {
+                final String item = "op@" + opIndex;
+                
assertThat(queueTarget.offer(item)).isEqualTo(queueRef.offer(item));
+            }
+
+            // Verify exit
+            assertThat(queueTarget.size()).isEqualTo(queueRef.size());
+            assertThat(queueTarget.peek()).isEqualTo(queueRef.peek());
+            assertThat(queueTarget).containsExactlyElementsOf(queueRef);
+        }
+    }
+}
diff --git 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java
new file mode 100644
index 0000000000..745e626395
--- /dev/null
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryRegistryTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static org.apache.logging.log4j.kit.recycler.Recycler.DEFAULT_CAPACITY;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import 
org.apache.logging.log4j.kit.recycler.internal.DummyRecyclerFactoryProvider.DummyRecyclerFactory;
+import 
org.apache.logging.log4j.kit.recycler.internal.QueueingRecyclerFactoryProvider.QueueingRecyclerFactory;
+import 
org.apache.logging.log4j.kit.recycler.internal.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+public class RecyclerFactoryRegistryTest {
+
+    @Test
+    void DummyRecyclerFactory_should_work() {
+        final RecyclerFactory factory = 
RecyclerFactoryTestUtil.createForEnvironment("dummy", null);
+        assertThat(factory).isInstanceOf(DummyRecyclerFactory.class);
+    }
+
+    @Test
+    void ThreadLocalRecyclerFactory_should_work() {
+        final RecyclerFactory factory = 
RecyclerFactoryTestUtil.createForEnvironment("threadLocal", null);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(DEFAULT_CAPACITY);
+    }
+
+    @Test
+    void ThreadLocalRecyclerFactory_should_work_with_capacity() {
+        final int capacity = 13;
+        final RecyclerFactory factory = 
RecyclerFactoryTestUtil.createForEnvironment("threadLocal", capacity);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(ThreadLocalRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(capacity);
+    }
+
+    @Test
+    void QueueingRecyclerFactory_should_work() {
+        final RecyclerFactory factory = 
RecyclerFactoryTestUtil.createForEnvironment("queue", null);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(DEFAULT_CAPACITY);
+    }
+
+    @Test
+    void QueueingRecyclerFactory_should_work_with_capacity() {
+        final int capacity = 100;
+        final RecyclerFactory factory = 
RecyclerFactoryTestUtil.createForEnvironment("queue", capacity);
+        assertThat(factory)
+                
.asInstanceOf(InstanceOfAssertFactories.type(QueueingRecyclerFactory.class))
+                .extracting(factory_ -> factory_.capacity)
+                .isEqualTo(capacity);
+    }
+
+    @Test
+    void verify_order() {
+        final RecyclerFactoryProvider dummyProvider = new 
DummyRecyclerFactoryProvider();
+        final RecyclerFactoryProvider threadLocalProvider = new 
ThreadLocalRecyclerFactoryProvider();
+        final RecyclerFactoryProvider queueProvider = new 
QueueingRecyclerFactoryProvider();
+        
assertThat(dummyProvider.getOrder()).isGreaterThan(queueProvider.getOrder());
+        
assertThat(queueProvider.getOrder()).isGreaterThan(threadLocalProvider.getOrder());
+    }
+}
diff --git 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryTestUtil.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryTestUtil.java
new file mode 100644
index 0000000000..19bcb48457
--- /dev/null
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/RecyclerFactoryTestUtil.java
@@ -0,0 +1,51 @@
+/*
+ * 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.kit.recycler.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+import org.apache.logging.log4j.kit.env.PropertyEnvironment;
+import org.apache.logging.log4j.kit.env.TestPropertyEnvironment;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ServiceLoaderUtil;
+import org.jspecify.annotations.Nullable;
+
+final class RecyclerFactoryTestUtil {
+
+    private RecyclerFactoryTestUtil() {}
+
+    static @Nullable RecyclerFactory createForEnvironment(final String 
factory, final @Nullable Integer capacity) {
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("Recycler.factory", factory);
+        if (capacity != null) {
+            properties.put("Recycler.capacity", capacity.toString());
+        }
+        final PropertyEnvironment env = new 
TestPropertyEnvironment(properties);
+        return ServiceLoaderUtil.safeStream(
+                        RecyclerFactoryProvider.class,
+                        ServiceLoader.load(
+                                RecyclerFactoryProvider.class, 
RecyclerFactoryTestUtil.class.getClassLoader()),
+                        StatusLogger.getLogger())
+                .filter(p -> factory.equals(p.getName()))
+                .findFirst()
+                .map(p -> p.createForEnvironment(env))
+                .orElse(null);
+    }
+}
diff --git 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProviderTest.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProviderTest.java
new file mode 100644
index 0000000000..f33afb1040
--- /dev/null
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/recycler/internal/ThreadLocalRecyclerFactoryProviderTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.kit.recycler.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Queue;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
+import 
org.apache.logging.log4j.kit.recycler.internal.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory;
+import 
org.apache.logging.log4j.kit.recycler.internal.ThreadLocalRecyclerFactoryProvider.ThreadLocalRecyclerFactory.ThreadLocalRecycler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junitpioneer.jupiter.params.IntRangeSource;
+
+class ThreadLocalRecyclerFactoryProviderTest {
+
+    private static final int CAPACITY = 13;
+
+    private static class RecyclableObject {}
+
+    private ThreadLocalRecycler<RecyclableObject> recycler;
+
+    private Queue<RecyclableObject> recyclerQueue;
+
+    @BeforeEach
+    void setUp() {
+        final RecyclerFactory recyclerFactory = 
RecyclerFactoryTestUtil.createForEnvironment("threadLocal", CAPACITY);
+        
assertThat(recyclerFactory).isInstanceOf(ThreadLocalRecyclerFactory.class);
+        assert recyclerFactory != null;
+        recycler = (ThreadLocalRecycler<RecyclableObject>) 
recyclerFactory.create(RecyclableObject::new);
+        recyclerQueue = recycler.queueRef.get();
+    }
+
+    @ParameterizedTest
+    @IntRangeSource(from = 1, to = CAPACITY, closed = true)
+    void nested_acquires_should_not_interfere(final int acquisitionCount) {
+
+        // pool should start empty
+        assertThat(recyclerQueue).isEmpty();
+
+        final List<RecyclableObject> acquiredObjects = IntStream.range(0, 
acquisitionCount)
+                .mapToObj(i -> recycler.acquire())
+                .collect(Collectors.toList());
+
+        // still nothing returned to pool
+        assertThat(recyclerQueue).isEmpty();
+
+        // don't want any duplicate instances
+        
assertThat(acquiredObjects).containsOnlyOnceElementsOf(acquiredObjects);
+        acquiredObjects.forEach(recycler::release);
+
+        // and now they should be back in the pool
+        assertThat(recyclerQueue).hasSize(acquisitionCount);
+
+        // then reacquire them to see that they're still the same object as 
we've filled in
+        // the thread-local queue with returned objects
+        final List<RecyclableObject> reacquiredObjects = IntStream.range(0, 
acquisitionCount)
+                .mapToObj(i -> recycler.acquire())
+                .collect(Collectors.toList());
+
+        
assertThat(reacquiredObjects).containsExactlyElementsOf(acquiredObjects);
+    }
+
+    @Test
+    void nested_acquires_past_max_queue_size_should_discard_extra_releases() {
+
+        assertThat(recyclerQueue).isEmpty();
+
+        // Simulate a callstack with excessive logging
+        final int acquisitionCount = Math.addExact(CAPACITY, 1024);
+        final List<RecyclableObject> acquiredObjects = IntStream.range(0, 
acquisitionCount)
+                .mapToObj(i -> recycler.acquire())
+                .toList();
+
+        // Verify collected instances are all new
+        assertThat(acquiredObjects).doesNotHaveDuplicates();
+
+        // Verify the pool is still empty
+        assertThat(recyclerQueue).isEmpty();
+
+        // Release all acquired instances
+        acquiredObjects.forEach(recycler::release);
+
+        // Verify the queue size is capped
+        assertThat(recyclerQueue).hasSize(CAPACITY);
+    }
+}
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
index 18b23d96c3..b9f3fdd198 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/JsonTemplateLayout.java
@@ -33,6 +33,7 @@ import 
org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.layout.ByteBufferDestination;
 import org.apache.logging.log4j.core.layout.Encoder;
 import org.apache.logging.log4j.core.layout.StringBuilderEncoder;
+import org.apache.logging.log4j.kit.recycler.Recycler;
 import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext;
 import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory;
 import 
org.apache.logging.log4j.layout.template.json.resolver.EventResolverInterceptor;
@@ -48,7 +49,6 @@ import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.plugins.PluginElement;
 import org.apache.logging.log4j.plugins.di.Key;
-import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.Strings;
 
 @Configurable(elementType = Layout.ELEMENT_TYPE)
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
index 77a80af09c..4ce28da6a7 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
@@ -22,8 +22,8 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.LockSupport;
 import java.util.function.Consumer;
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.kit.recycler.Recycler;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.recycler.Recycler;
 
 /**
  * Resolves a number from an internal counter.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
index 0f7fc723cc..cda477d215 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/MessageParameterResolver.java
@@ -17,11 +17,11 @@
 package org.apache.logging.log4j.layout.template.json.resolver;
 
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.kit.recycler.Recycler;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ParameterConsumer;
 import org.apache.logging.log4j.message.ParameterVisitable;
-import org.apache.logging.log4j.spi.recycler.Recycler;
 
 /**
  * {@link Message} parameter (i.e., {@link Message#getParameters()}) resolver.
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
index 64356bfe2e..603692bf7c 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/ReadOnlyStringMapResolver.java
@@ -21,9 +21,9 @@ import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.TriConsumer;
 
diff --git 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
index 8d505eff8f..dddf66f727 100644
--- 
a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
+++ 
b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/StackTraceStringResolver.java
@@ -22,11 +22,11 @@ import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import org.apache.logging.log4j.kit.recycler.Recycler;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactory;
 import org.apache.logging.log4j.layout.template.json.util.CharSequencePointer;
 import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
 import 
org.apache.logging.log4j.layout.template.json.util.TruncatingBufferedPrintWriter;
-import org.apache.logging.log4j.spi.recycler.Recycler;
-import org.apache.logging.log4j.spi.recycler.RecyclerFactory;
 
 /**
  * Exception stack trace to JSON string resolver used by {@link 
ExceptionResolver}.
diff --git 
a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
 
b/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
deleted file mode 100644
index c66b5c946a..0000000000
--- 
a/log4j-to-slf4j/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.logging.slf4j.SLF4JProvider
\ No newline at end of file

Reply via email to