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

pkarwasz pushed a commit to branch fix/apply-property-environment
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

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

    Move `Recycler` to `log4j-kit`
---
 .../services/org.apache.logging.log4j.spi.Provider |   1 -
 .../logging/log4j/async/logger/AsyncLogger.java    |   4 +-
 .../java/org/apache/logging/log4j/core/Logger.java |   2 +-
 .../logging/log4j/core/config/Configuration.java   |   2 +-
 .../log4j/core/filter/StructuredDataFilter.java    |   4 +-
 .../logging/log4j/core/impl/DefaultBundle.java     |   2 +-
 .../logging/log4j/core/impl/MutableLogEvent.java   |   2 +-
 .../log4j/core/impl/ReusableLogEventFactory.java   |   4 +-
 .../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  |   2 +-
 .../jctools/JCToolsRecyclerFactoryProvider.java    |  19 ++-
 .../JCToolsRecyclerFactoryProviderTest.java        |   4 +-
 ...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 ++--
 .../message/internal/ReusableMessageFactory.java   | 189 +++++++++++++++++++++
 .../logging/log4j/kit/message/package-info.java    |  22 +++
 .../logging/log4j/kit/recycler/PropertyKeys.java   |  29 ++--
 .../logging/log4j/kit/recycler/Recycler.java       |  48 ++++++
 .../logging/log4j/kit/recycler/RecyclerAware.java  |  27 +--
 .../log4j/kit/recycler/RecyclerFactory.java        |  58 +++++++
 .../kit/recycler/RecyclerFactoryProvider.java      |  59 +++++++
 .../log4j/kit/recycler/internal/ArrayQueue.java    | 100 +++++++++++
 .../internal/DummyRecyclerFactoryProvider.java     |  81 +++++++++
 .../internal/QueueingRecyclerFactoryProvider.java  |  51 +++---
 .../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 ++--
 .../PropertiesUtilPropertyEnvironmentTest.java     |   2 -
 .../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 -
 pom.xml                                            |   8 +-
 48 files changed, 1259 insertions(+), 166 deletions(-)

diff --git 
a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
 
b/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
deleted file mode 100644
index 5ae649a3f9..0000000000
--- 
a/log4j-api-test/src/test/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.logging.log4j.TestProvider
\ No newline at end of file
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/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/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..206e8bb7dd 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
@@ -40,6 +40,7 @@ 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.recycler.RecyclerFactory;
 import org.apache.logging.log4j.message.FlowMessageFactory;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.plugins.Named;
@@ -52,7 +53,6 @@ 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;
 
 /**
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-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 4acb136788..93df4c6430 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.LogEventFactory;
 import org.apache.logging.log4j.core.impl.PropertyKeys;
 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 c1332afdae..8343082e34 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
index a60d6e4c9c..ad86c88089 100644
--- 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
@@ -16,8 +16,8 @@
  */
 package org.apache.logging.log4j.core.util;
 
+import org.apache.logging.log4j.kit.recycler.Recycler;
 import org.apache.logging.log4j.spi.LoggingSystem;
-import org.apache.logging.log4j.spi.recycler.Recycler;
 import org.apache.logging.log4j.util.Lazy;
 
 /**
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 0cf6daa496..b5671d2fa7 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,18 +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.kit.env.PropertyEnvironment;
-import org.apache.logging.log4j.spi.LoggingSystemProperty;
-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.kit.recycler.PropertyKeys;
+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;
 import org.jctools.queues.MpmcArrayQueue;
 
 /**
@@ -52,11 +52,12 @@ public final class JCToolsRecyclerFactoryProvider 
implements RecyclerFactoryProv
     @Override
     public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
         requireNonNull(environment, "environment");
-        final int capacity = 
environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, 
DEFAULT_CAPACITY);
-        if (capacity < 1) {
+        final Integer capacity =
+                
environment.getProperty(PropertyKeys.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..0a8cba1bbb 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,8 +20,8 @@ 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.apache.logging.log4j.kit.recycler.RecyclerFactoryProvider;
+import org.apache.logging.log4j.kit.recycler.RecyclerFactoryRegistry;
 import org.junit.jupiter.api.Test;
 
 class JCToolsRecyclerFactoryProviderTest {
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/internal/ReusableMessageFactory.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/internal/ReusableMessageFactory.java
new file mode 100644
index 0000000000..7bdc19c01c
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/message/internal/ReusableMessageFactory.java
@@ -0,0 +1,189 @@
+/*
+ * 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.message.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.ReusableMessage;
+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;
+
+/**
+ * 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.
+ * @see Recycler
+ * @since 3.0.0
+ */
+@PerformanceSensitive("allocation")
+public final class ReusableMessageFactory implements RecyclingMessageFactory {
+
+    private final Recycler<ReusableParameterizedMessage> 
parameterizedMessageRecycler;
+    private final Recycler<ReusableSimpleMessage> simpleMessageRecycler;
+    private final Recycler<ReusableObjectMessage> objectMessageRecycler;
+
+    public ReusableMessageFactory(final RecyclerFactory recyclerFactory) {
+        parameterizedMessageRecycler =
+                recyclerFactory.create(ReusableParameterizedMessage::new, 
ReusableParameterizedMessage::clear);
+        simpleMessageRecycler = 
recyclerFactory.create(ReusableSimpleMessage::new, 
ReusableSimpleMessage::clear);
+        objectMessageRecycler = 
recyclerFactory.create(ReusableObjectMessage::new, 
ReusableObjectMessage::clear);
+    }
+
+    @Override
+    public void recycle(final Message message) {
+        if (message instanceof final ReusableMessage reusable) {
+            reusable.clear();
+        }
+        // related to LOG4J2-1583 and nested log messages clobbering each 
other. recycle messages today!
+        if (message instanceof final ReusableParameterizedMessage reusable) {
+            parameterizedMessageRecycler.release(reusable);
+        } else if (message instanceof final ReusableObjectMessage reusable) {
+            objectMessageRecycler.release(reusable);
+        } else if (message instanceof final ReusableSimpleMessage reusable) {
+            simpleMessageRecycler.release(reusable);
+        }
+    }
+
+    @Override
+    public Message newMessage(final CharSequence charSequence) {
+        final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
+        result.set(charSequence);
+        return result;
+    }
+
+    @Override
+    public Message newMessage(final String message, final Object... params) {
+        return parameterizedMessageRecycler.acquire().set(message, params);
+    }
+
+    @Override
+    public Message newMessage(final String message, final Object p0) {
+        return parameterizedMessageRecycler.acquire().set(message, p0);
+    }
+
+    @Override
+    public Message newMessage(final String message, final Object p0, final 
Object p1) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1);
+    }
+
+    @Override
+    public Message newMessage(final String message, final Object p0, final 
Object p1, final Object p2) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message, final Object p0, final Object p1, final 
Object p2, final Object p3) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message, final Object p0, final Object p1, final 
Object p2, final Object p3, final Object p4) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message,
+            final Object p0,
+            final Object p1,
+            final Object p2,
+            final Object p3,
+            final Object p4,
+            final Object p5) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4, p5);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message,
+            final Object p0,
+            final Object p1,
+            final Object p2,
+            final Object p3,
+            final Object p4,
+            final Object p5,
+            final Object p6) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4, p5, p6);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message,
+            final Object p0,
+            final Object p1,
+            final Object p2,
+            final Object p3,
+            final Object p4,
+            final Object p5,
+            final Object p6,
+            final Object p7) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4, p5, p6, p7);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message,
+            final Object p0,
+            final Object p1,
+            final Object p2,
+            final Object p3,
+            final Object p4,
+            final Object p5,
+            final Object p6,
+            final Object p7,
+            final Object p8) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4, p5, p6, p7, p8);
+    }
+
+    @Override
+    public Message newMessage(
+            final String message,
+            final Object p0,
+            final Object p1,
+            final Object p2,
+            final Object p3,
+            final Object p4,
+            final Object p5,
+            final Object p6,
+            final Object p7,
+            final Object p8,
+            final Object p9) {
+        return parameterizedMessageRecycler.acquire().set(message, p0, p1, p2, 
p3, p4, p5, p6, p7, p8, p9);
+    }
+
+    @Override
+    public Message newMessage(final String message) {
+        final ReusableSimpleMessage result = simpleMessageRecycler.acquire();
+        result.set(message);
+        return result;
+    }
+
+    @Override
+    public Message newMessage(final Object message) {
+        final ReusableObjectMessage result = objectMessageRecycler.acquire();
+        result.set(message);
+        return result;
+    }
+}
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-jctools/src/test/java/org/apache/logging/log4j/jctools/JCToolsRecyclerFactoryProviderTest.java
 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/PropertyKeys.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/PropertyKeys.java
index 51e9d9e272..77b1d06939 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/PropertyKeys.java
@@ -14,24 +14,19 @@
  * 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 org.apache.logging.log4j.kit.env.Log4jProperty;
+import org.jspecify.annotations.Nullable;
 
-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 PropertyKeys {
 
-class JCToolsRecyclerFactoryProviderTest {
-
-    @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);
-    }
+    /**
+     * A set of common configuration options for recyclers
+     *
+     * @param factory The name of the recycler factory to use (cf. {@link 
RecyclerFactoryProvider#getName()}),
+     * @param capacity The capacity of the recycler.
+     */
+    @Log4jProperty
+    public record Recycler(@Nullable String factory, @Nullable Integer 
capacity) {}
 }
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..ae8e140134
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/RecyclerFactoryProvider.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+/**
+ * Contract for providing {@link RecyclerFactory} instances.
+ *
+ * @since 3.0.0
+ */
+public interface RecyclerFactoryProvider {
+
+    /**
+     * 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..0922bb2d03
--- /dev/null
+++ 
b/log4j-kit/src/main/java/org/apache/logging/log4j/kit/recycler/internal/DummyRecyclerFactoryProvider.java
@@ -0,0 +1,81 @@
+/*
+ * 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 {
+
+    @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 59%
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 0cf6daa496..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,74 +14,75 @@
  * 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.kit.env.PropertyEnvironment;
-import org.apache.logging.log4j.spi.LoggingSystemProperty;
-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.jctools.queues.MpmcArrayQueue;
+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
     public RecyclerFactory createForEnvironment(final PropertyEnvironment 
environment) {
         requireNonNull(environment, "environment");
-        final int capacity = 
environment.getIntegerProperty(LoggingSystemProperty.RECYCLER_CAPACITY, 
DEFAULT_CAPACITY);
+        final int capacity = 
environment.getIntegerProperty("Recycler.capacity", DEFAULT_CAPACITY);
         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..23af67bec1
--- /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.PropertyKeys;
+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 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(PropertyKeys.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/internal/PropertiesUtilPropertyEnvironmentTest.java
 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/internal/PropertiesUtilPropertyEnvironmentTest.java
index 2c9b8b9c1d..3ec5d184e0 100644
--- 
a/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/internal/PropertiesUtilPropertyEnvironmentTest.java
+++ 
b/log4j-kit/src/test/java/org/apache/logging/log4j/kit/env/internal/PropertiesUtilPropertyEnvironmentTest.java
@@ -18,11 +18,9 @@ package org.apache.logging.log4j.kit.env.internal;
 
 import org.apache.logging.log4j.kit.env.PropertyEnvironment;
 import org.junit.jupiter.api.Test;
-import org.junitpioneer.jupiter.DisabledUntil;
 import org.junitpioneer.jupiter.SetEnvironmentVariable;
 import org.junitpioneer.jupiter.SetSystemProperty;
 
-@DisabledUntil(date = "2024-04-01", reason = "Disable until `log4j-api` 3.x is 
removed")
 class PropertiesUtilPropertyEnvironmentTest extends AbstractPropertyNamesTest {
 
     @Test
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 4716ef565c..0894e72625 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
@@ -26,12 +26,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;
@@ -204,23 +203,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
diff --git a/pom.xml b/pom.xml
index e2a6286227..ba974efa04 100644
--- a/pom.xml
+++ b/pom.xml
@@ -233,8 +233,6 @@
     <!-- Last comes the rest of the modules in alphabetical order.
          Note that modules here must have a corresponding entry in 
`dependencyManagement > dependencies` block below! -->
     <module>log4j-1.2-api</module>
-    <module>log4j-api</module>
-    <module>log4j-api-test</module>
     <module>log4j-async-logger</module>
     <module>log4j-config-jackson</module>
     <module>log4j-config-properties</module>
@@ -267,8 +265,6 @@
     <module>log4j-slf4j2-impl</module>
     <module>log4j-slf4j-impl</module>
     <module>log4j-spring-cloud-config-client</module>
-    <module>log4j-to-jul</module>
-    <module>log4j-to-slf4j</module>
 
   </modules>
 
@@ -359,13 +355,13 @@
       <dependency>
         <groupId>org.apache.logging.log4j</groupId>
         <artifactId>log4j-api</artifactId>
-        <version>${project.version}</version>
+        <version>2.24.0-SNAPSHOT</version>
       </dependency>
 
       <dependency>
         <groupId>org.apache.logging.log4j</groupId>
         <artifactId>log4j-api-test</artifactId>
-        <version>${project.version}</version>
+        <version>2.24.0-SNAPSHOT</version>
       </dependency>
 
       <dependency>

Reply via email to