You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ma...@apache.org on 2022/03/27 02:51:53 UTC

[logging-log4j2] 03/03: Make TypeConverter plugins injectable

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

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

commit e8fdd738da0e7f67dbb04c4baaa400aa4ca7c0c2
Author: Matt Sicker <ma...@apache.org>
AuthorDate: Sat Mar 26 21:51:29 2022 -0500

    Make TypeConverter plugins injectable
    
    This adds Injector::getTypeConverter to replace some old utility classes for holding static state.
    
    Signed-off-by: Matt Sicker <ma...@apache.org>
---
 .../logging/log4j/cassandra/CassandraManager.java  |  16 +-
 .../config/plugins/convert/TypeConvertersTest.java |  28 +--
 .../plugins/convert/CoreTypeConvertersTest.java    |  25 ++-
 .../log4j/core/appender/db/ColumnMapping.java      |  24 ++-
 .../config/plugins/convert/CoreTypeConverters.java |  37 ++--
 .../plugins/visit/PluginAttributeVisitor.java      |  18 +-
 .../visit/PluginBuilderAttributeVisitor.java       |   7 +-
 .../logging/log4j/couchdb/CouchDbProvider.java     |  14 +-
 .../log4j/jdbc/appender/JdbcDatabaseManager.java   |  40 ++--
 .../json/util/RecyclerFactoryConverter.java        |   3 +-
 .../template/json/util/RecyclerFactoriesTest.java  |  13 +-
 .../logging/log4j/mongodb3/MongoDb3Provider.java   |  33 ++--
 .../plugins/convert/TypeConverterRegistryTest.java |  22 ++-
 .../log4j/plugins/convert/TypeConverter.java       |  38 +++-
 .../plugins/convert/TypeConverterRegistry.java     | 210 ---------------------
 .../log4j/plugins/convert/TypeConverters.java      |  96 ----------
 .../logging/log4j/plugins/di/DefaultInjector.java  | 128 +++++++++++++
 .../apache/logging/log4j/plugins/di/Injector.java  |  11 ++
 .../validation/validators/ValidPortValidator.java  |  15 +-
 .../plugins/visit/PluginAttributeVisitor.java      |  18 +-
 .../visit/PluginBuilderAttributeVisitor.java       |  15 +-
 .../asciidoc/manual/json-template-layout.adoc.vm   |   3 +-
 22 files changed, 380 insertions(+), 434 deletions(-)

diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
index 7ae8393..88f87d7 100644
--- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
+++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
@@ -16,12 +16,6 @@
  */
 package org.apache.logging.log4j.cassandra;
 
-import java.io.Serializable;
-import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
 import com.datastax.driver.core.BatchStatement;
 import com.datastax.driver.core.BoundStatement;
 import com.datastax.driver.core.Cluster;
@@ -31,7 +25,6 @@ import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.appender.ManagerFactory;
 import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
 import org.apache.logging.log4j.core.appender.db.ColumnMapping;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
 import org.apache.logging.log4j.core.net.SocketAddress;
 import org.apache.logging.log4j.jdbc.convert.DateTypeConverter;
 import org.apache.logging.log4j.spi.ThreadContextMap;
@@ -39,6 +32,12 @@ import org.apache.logging.log4j.spi.ThreadContextStack;
 import org.apache.logging.log4j.util.ReadOnlyStringMap;
 import org.apache.logging.log4j.util.Strings;
 
+import java.io.Serializable;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
 /**
  * Manager for a Cassandra appender instance.
  */
@@ -99,8 +98,7 @@ public class CassandraManager extends AbstractDatabaseManager {
             } else if (Date.class.isAssignableFrom(columnMapping.getType())) {
                 values[i] = DateTypeConverter.fromMillis(event.getTimeMillis(), columnMapping.getType().asSubclass(Date.class));
             } else {
-                values[i] = TypeConverters.convert(columnMapping.getLayout().toSerializable(event),
-                    columnMapping.getType(), null);
+                values[i] = columnMapping.getTypeConverter().convert(columnMapping.getLayout().toSerializable(event), null);
             }
         }
         final BoundStatement boundStatement = preparedStatement.bind(values);
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java
index 6e5e62f..0416d32 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java
@@ -17,6 +17,17 @@
 
 package org.apache.logging.log4j.core.config.plugins.convert;
 
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.appender.rolling.action.Duration;
+import org.apache.logging.log4j.core.layout.GelfLayout;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.plugins.di.DI;
+import org.apache.logging.log4j.plugins.di.Injector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
 import java.io.File;
 import java.math.BigDecimal;
 import java.math.BigInteger;
@@ -33,17 +44,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.UUID;
 
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.appender.rolling.action.Duration;
-import org.apache.logging.log4j.core.layout.GelfLayout;
-import org.apache.logging.log4j.core.net.Facility;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 
 /**
  * Tests {@link CoreTypeConverters}.
@@ -216,7 +218,9 @@ public class TypeConvertersTest {
 
     @Test
     public void testConvert() throws Exception {
-        final Object actual = TypeConverters.convert(value, clazz, defaultValue);
+        final Injector injector = DI.createInjector();
+        injector.init();
+        final Object actual = injector.getTypeConverter(clazz).convert(value, defaultValue);
         final String assertionMessage = "\nGiven: " + value + "\nDefault: " + defaultValue;
         if (expected != null && expected instanceof char[]) {
             assertArrayEquals(assertionMessage, (char[]) expected, (char[]) actual);
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java
index 117ceea..1f13210 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/plugins/convert/CoreTypeConvertersTest.java
@@ -17,29 +17,37 @@
 package org.apache.logging.log4j.core.plugins.convert;
 
 import org.apache.logging.log4j.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.plugins.convert.TypeConverterRegistry;
+import org.apache.logging.log4j.plugins.di.DI;
+import org.apache.logging.log4j.plugins.di.Injector;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
 
 public class CoreTypeConvertersTest {
 
+    private final Injector injector = DI.createInjector();
+
+    @BeforeEach
+    void setUp() {
+        injector.init();
+    }
+
     @Test
     public void testFindNullConverter() {
-        assertThrows(NullPointerException.class,
-                () -> TypeConverterRegistry.getInstance().findCompatibleConverter(null));
+        assertThrows(NullPointerException.class, () -> injector.getTypeConverter(null));
     }
 
     @Test
     public void testFindBooleanConverter() throws Exception {
-        final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.class);
+        final TypeConverter<?> converter = injector.getTypeConverter(Boolean.class);
         assertNotNull(converter);
         assertTrue((Boolean) converter.convert("TRUE"));
     }
 
     @Test
     public void testFindPrimitiveBooleanConverter() throws Exception {
-        final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.TYPE);
+        final TypeConverter<?> converter = injector.getTypeConverter(Boolean.TYPE);
         assertNotNull(converter);
         assertTrue((Boolean) converter.convert("tRUe"));
     }
@@ -48,7 +56,7 @@ public class CoreTypeConvertersTest {
     @Test
     public void testFindCharSequenceConverterUsingStringConverter() throws Exception {
         final TypeConverter<CharSequence> converter = (TypeConverter<CharSequence>)
-            TypeConverterRegistry.getInstance().findCompatibleConverter(CharSequence.class);
+                injector.getTypeConverter(CharSequence.class);
         assertNotNull(converter);
         final CharSequence expected = "This is a test sequence of characters";
         final CharSequence actual = converter.convert(expected.toString());
@@ -59,7 +67,7 @@ public class CoreTypeConvertersTest {
     @Test
     public void testFindNumberConverter() throws Exception {
         final TypeConverter<Number> numberTypeConverter = (TypeConverter<Number>)
-            TypeConverterRegistry.getInstance().findCompatibleConverter(Number.class);
+                injector.getTypeConverter(Number.class);
         assertNotNull(numberTypeConverter);
         // TODO: is there a specific converter this should return?
     }
@@ -71,8 +79,7 @@ public class CoreTypeConvertersTest {
     @SuppressWarnings("unchecked")
     @Test
     public void testFindEnumConverter() throws Exception {
-        final TypeConverter<Foo> fooTypeConverter = (TypeConverter<Foo>)
-            TypeConverterRegistry.getInstance().findCompatibleConverter(Foo.class);
+        final TypeConverter<Foo> fooTypeConverter = (TypeConverter<Foo>) injector.getTypeConverter(Foo.class);
         assertNotNull(fooTypeConverter);
         assertEquals(Foo.I, fooTypeConverter.convert("i"));
         assertEquals(Foo.PITY, fooTypeConverter.convert("pity"));
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
index 740ebc8..a58e4e2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
@@ -22,10 +22,13 @@ import org.apache.logging.log4j.core.StringLayout;
 import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.plugins.Inject;
 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.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.validation.constraints.Required;
 import org.apache.logging.log4j.spi.ThreadContextMap;
 import org.apache.logging.log4j.spi.ThreadContextStack;
@@ -34,6 +37,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
 
 import java.util.Date;
 import java.util.Locale;
+import java.util.function.Supplier;
 
 /**
  * A configuration element for specifying a database column name mapping.
@@ -74,6 +78,8 @@ public class ColumnMapping {
         @Required(message = "No conversion type provided")
         private Class<?> type = String.class;
 
+        private Injector injector;
+
         @Override
         public ColumnMapping build() {
             if (pattern != null) {
@@ -96,7 +102,7 @@ public class ColumnMapping {
                 LOGGER.error("Only one of 'literal' or 'parameter' can be set on the column mapping {}", this);
                 return null;
             }
-            return new ColumnMapping(name, source, layout, literal, parameter, type);
+            return new ColumnMapping(name, source, layout, literal, parameter, type, () -> injector.getTypeConverter(type));
         }
 
         public Builder setConfiguration(final Configuration configuration) {
@@ -181,6 +187,12 @@ public class ColumnMapping {
             return this;
         }
 
+        @Inject
+        public Builder setInjector(final Injector injector) {
+            this.injector = injector;
+            return this;
+        }
+
         @Override
         public String toString() {
             return "Builder [name=" + name + ", source=" + source + ", literal=" + literal + ", parameter=" + parameter
@@ -206,8 +218,11 @@ public class ColumnMapping {
     private final String parameter;
     private final String source;
     private final Class<?> type;
+    private final Supplier<TypeConverter<?>> typeConverter;
 
-    private ColumnMapping(final String name, final String source, final StringLayout layout, final String literalValue, final String parameter, final Class<?> type) {
+    private ColumnMapping(
+            final String name, final String source, final StringLayout layout, final String literalValue,
+            final String parameter, final Class<?> type, final Supplier<TypeConverter<?>> typeConverter) {
         this.name = name;
         this.nameKey = toKey(name);
         this.source = source;
@@ -215,6 +230,7 @@ public class ColumnMapping {
         this.literalValue = literalValue;
         this.parameter = parameter;
         this.type = type;
+        this.typeConverter = typeConverter;
     }
 
     public StringLayout getLayout() {
@@ -245,6 +261,10 @@ public class ColumnMapping {
         return type;
     }
 
+    public TypeConverter<?> getTypeConverter() {
+        return typeConverter.get();
+    }
+
     @Override
     public String toString() {
         return "ColumnMapping [name=" + name + ", source=" + source + ", literalValue=" + literalValue + ", parameter="
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java
index 352f347..dcf1804 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java
@@ -22,7 +22,6 @@ import org.apache.logging.log4j.core.appender.rolling.action.Duration;
 import org.apache.logging.log4j.core.util.CronExpression;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
 import org.apache.logging.log4j.util.LoaderUtil;
 
 import java.io.File;
@@ -55,7 +54,7 @@ public final class CoreTypeConverters {
     /**
      * Parses a {@link String} into a {@link BigDecimal}.
      */
-    @Plugin(name = "BigDecimal", category = TypeConverters.CATEGORY)
+    @Plugin(name = "BigDecimal", category = TypeConverter.CATEGORY)
     public static class BigDecimalConverter implements TypeConverter<BigDecimal> {
         @Override
         public BigDecimal convert(final String s) {
@@ -66,7 +65,7 @@ public final class CoreTypeConverters {
     /**
      * Parses a {@link String} into a {@link BigInteger}.
      */
-    @Plugin(name = "BigInteger", category = TypeConverters.CATEGORY)
+    @Plugin(name = "BigInteger", category = TypeConverter.CATEGORY)
     public static class BigIntegerConverter implements TypeConverter<BigInteger> {
         @Override
         public BigInteger convert(final String s) {
@@ -84,7 +83,7 @@ public final class CoreTypeConverters {
      * <li>String using {@link Charset#defaultCharset()} [TODO Should this be UTF-8 instead?]</li>
      * </ul>
      */
-    @Plugin(name = "ByteArray", category = TypeConverters.CATEGORY)
+    @Plugin(name = "ByteArray", category = TypeConverter.CATEGORY)
     public static class ByteArrayConverter implements TypeConverter<byte[]> {
 
         private static final String PREFIX_0x = "0x";
@@ -111,7 +110,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@code char[]}.
      */
-    @Plugin(name = "CharacterArray", category = TypeConverters.CATEGORY)
+    @Plugin(name = "CharacterArray", category = TypeConverter.CATEGORY)
     public static class CharArrayConverter implements TypeConverter<char[]> {
         @Override
         public char[] convert(final String s) {
@@ -122,7 +121,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link Charset}.
      */
-    @Plugin(name = "Charset", category = TypeConverters.CATEGORY)
+    @Plugin(name = "Charset", category = TypeConverter.CATEGORY)
     public static class CharsetConverter implements TypeConverter<Charset> {
         @Override
         public Charset convert(final String s) {
@@ -133,7 +132,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link Class}.
      */
-    @Plugin(name = "Class", category = TypeConverters.CATEGORY)
+    @Plugin(name = "Class", category = TypeConverter.CATEGORY)
     public static class ClassConverter implements TypeConverter<Class<?>> {
         @Override
         public Class<?> convert(final String s) throws ClassNotFoundException {
@@ -163,7 +162,7 @@ public final class CoreTypeConverters {
         }
     }
 
-    @Plugin(name = "CronExpression", category = TypeConverters.CATEGORY)
+    @Plugin(name = "CronExpression", category = TypeConverter.CATEGORY)
     public static class CronExpressionConverter implements TypeConverter<CronExpression> {
         @Override
         public CronExpression convert(final String s) throws Exception {
@@ -175,7 +174,7 @@ public final class CoreTypeConverters {
      * Converts a {@link String} into a {@link Duration}.
      * @since 2.5
      */
-    @Plugin(name = "Duration", category = TypeConverters.CATEGORY)
+    @Plugin(name = "Duration", category = TypeConverter.CATEGORY)
     public static class DurationConverter implements TypeConverter<Duration> {
         @Override
         public Duration convert(final String s) {
@@ -186,7 +185,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link File}.
      */
-    @Plugin(name = "File", category = TypeConverters.CATEGORY)
+    @Plugin(name = "File", category = TypeConverter.CATEGORY)
     public static class FileConverter implements TypeConverter<File> {
         @Override
         public File convert(final String s) {
@@ -197,7 +196,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into an {@link InetAddress}.
      */
-    @Plugin(name = "InetAddress", category = TypeConverters.CATEGORY)
+    @Plugin(name = "InetAddress", category = TypeConverter.CATEGORY)
     public static class InetAddressConverter implements TypeConverter<InetAddress> {
         @Override
         public InetAddress convert(final String s) throws Exception {
@@ -208,7 +207,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names.
      */
-    @Plugin(name = "Level", category = TypeConverters.CATEGORY)
+    @Plugin(name = "Level", category = TypeConverter.CATEGORY)
     public static class LevelConverter implements TypeConverter<Level> {
         @Override
         public Level convert(final String s) {
@@ -220,7 +219,7 @@ public final class CoreTypeConverters {
      * Converts a {@link String} into a {@link Path}.
      * @since 2.8
      */
-    @Plugin(name = "Path", category = TypeConverters.CATEGORY)
+    @Plugin(name = "Path", category = TypeConverter.CATEGORY)
     public static class PathConverter implements TypeConverter<Path> {
         @Override
         public Path convert(final String s) throws Exception {
@@ -231,7 +230,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link Pattern}.
      */
-    @Plugin(name = "Pattern", category = TypeConverters.CATEGORY)
+    @Plugin(name = "Pattern", category = TypeConverter.CATEGORY)
     public static class PatternConverter implements TypeConverter<Pattern> {
         @Override
         public Pattern convert(final String s) {
@@ -242,7 +241,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link Provider}.
      */
-    @Plugin(name = "SecurityProvider", category = TypeConverters.CATEGORY)
+    @Plugin(name = "SecurityProvider", category = TypeConverter.CATEGORY)
     public static class SecurityProviderConverter implements TypeConverter<Provider> {
         @Override
         public Provider convert(final String s) {
@@ -253,7 +252,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link URI}.
      */
-    @Plugin(name = "URI", category = TypeConverters.CATEGORY)
+    @Plugin(name = "URI", category = TypeConverter.CATEGORY)
     public static class UriConverter implements TypeConverter<URI> {
         @Override
         public URI convert(final String s) throws URISyntaxException {
@@ -264,7 +263,7 @@ public final class CoreTypeConverters {
     /**
      * Converts a {@link String} into a {@link URL}.
      */
-    @Plugin(name = "URL", category = TypeConverters.CATEGORY)
+    @Plugin(name = "URL", category = TypeConverter.CATEGORY)
     public static class UrlConverter implements TypeConverter<URL> {
         @Override
         public URL convert(final String s) throws MalformedURLException {
@@ -276,7 +275,7 @@ public final class CoreTypeConverters {
      * Converts a {@link String} into a {@link UUID}.
      * @since 2.8
      */
-    @Plugin(name = "UUID", category = TypeConverters.CATEGORY)
+    @Plugin(name = "UUID", category = TypeConverter.CATEGORY)
     public static class UuidConverter implements TypeConverter<UUID> {
         @Override
         public UUID convert(final String s) throws Exception {
@@ -284,7 +283,7 @@ public final class CoreTypeConverters {
         }
     }
 
-    @Plugin(name = "ZoneId", category = TypeConverters.CATEGORY)
+    @Plugin(name = "ZoneId", category = TypeConverter.CATEGORY)
     public static class ZoneIdConverter implements TypeConverter<ZoneId> {
         @Override
         public ZoneId convert(final String s) throws Exception {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java
index 798a400..4e138a8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginAttributeVisitor.java
@@ -21,7 +21,8 @@ import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
 import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Node;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Keys;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
@@ -60,10 +61,14 @@ public class PluginAttributeVisitor implements NodeVisitor {
     );
 
     private final Function<String, String> stringSubstitutionStrategy;
+    private final Injector injector;
 
     @Inject
-    public PluginAttributeVisitor(@Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy) {
+    public PluginAttributeVisitor(
+            @Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy,
+            final Injector injector) {
         this.stringSubstitutionStrategy = stringSubstitutionStrategy;
+        this.injector = injector;
     }
 
     @Override
@@ -71,10 +76,11 @@ public class PluginAttributeVisitor implements NodeVisitor {
         final String name = AnnotatedElementNameProvider.getName(field);
         final Collection<String> aliases = AnnotatedElementAliasesProvider.getAliases(field);
         final Type targetType = field.getGenericType();
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final PluginAttribute annotation = field.getAnnotation(PluginAttribute.class);
         final boolean sensitive = annotation.sensitive();
         final Object value = node.removeMatchingAttribute(name, aliases)
-                .map(stringSubstitutionStrategy.andThen(s -> TypeConverters.convert(s, targetType, null, sensitive)))
+                .map(stringSubstitutionStrategy.andThen(s -> (Object) converter.convert(s, null, sensitive)))
                 .orElseGet(() -> getDefaultValue(targetType, annotation));
         StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", ");
         return value;
@@ -85,10 +91,11 @@ public class PluginAttributeVisitor implements NodeVisitor {
         final String name = AnnotatedElementNameProvider.getName(parameter);
         final Collection<String> aliases = AnnotatedElementAliasesProvider.getAliases(parameter);
         final Type targetType = parameter.getParameterizedType();
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final PluginAttribute annotation = parameter.getAnnotation(PluginAttribute.class);
         final boolean sensitive = annotation.sensitive();
         final Object value = node.removeMatchingAttribute(name, aliases)
-                .map(stringSubstitutionStrategy.andThen(s -> TypeConverters.convert(s, targetType, null, sensitive)))
+                .map(stringSubstitutionStrategy.andThen(s -> (Object) converter.convert(s, null, sensitive)))
                 .orElseGet(() -> getDefaultValue(targetType, annotation));
         StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", ");
         return value;
@@ -99,7 +106,8 @@ public class PluginAttributeVisitor implements NodeVisitor {
         if (extractor != null) {
             return extractor.apply(annotation);
         }
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final var value = stringSubstitutionStrategy.apply(annotation.defaultString());
-        return Strings.isEmpty(value) ? null : TypeConverters.convert(value, targetType, null, false);
+        return Strings.isEmpty(value) ? null : converter.convert(value, null);
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java
index 9d6fe54..658b5f1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visit/PluginBuilderAttributeVisitor.java
@@ -20,6 +20,7 @@ package org.apache.logging.log4j.core.config.plugins.visit;
 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Keys;
 
 import java.lang.reflect.AnnotatedElement;
@@ -27,8 +28,10 @@ import java.util.function.Function;
 
 public class PluginBuilderAttributeVisitor extends org.apache.logging.log4j.plugins.visit.PluginBuilderAttributeVisitor {
     @Inject
-    public PluginBuilderAttributeVisitor(@Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy) {
-        super(stringSubstitutionStrategy);
+    public PluginBuilderAttributeVisitor(
+            @Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy,
+            final Injector injector) {
+        super(stringSubstitutionStrategy, injector);
     }
 
     @SuppressWarnings("deprecation")
diff --git a/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java b/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java
index 179096c..ad79830 100644
--- a/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java
+++ b/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java
@@ -16,14 +16,14 @@
  */
 package org.apache.logging.log4j.couchdb;
 
-import java.lang.reflect.Method;
-
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginAttribute;
 import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
 import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
 import org.apache.logging.log4j.status.StatusLogger;
@@ -32,6 +32,8 @@ import org.apache.logging.log4j.util.Strings;
 import org.lightcouch.CouchDbClient;
 import org.lightcouch.CouchDbProperties;
 
+import java.lang.reflect.Method;
+
 /**
  * The Apache CouchDB implementation of {@link NoSqlProvider}.
  */
@@ -89,7 +91,8 @@ public final class CouchDbProvider implements NoSqlProvider<CouchDbConnection> {
             @PluginAttribute final String username,
             @PluginAttribute(sensitive = true) final String password,
             @PluginAttribute final String factoryClassName,
-            @PluginAttribute final String factoryMethodName) {
+            @PluginAttribute final String factoryMethodName,
+            final Injector injector) {
         CouchDbClient client;
         String description;
         if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
@@ -139,7 +142,8 @@ public final class CouchDbProvider implements NoSqlProvider<CouchDbConnection> {
                 LOGGER.warn("No protocol specified, using default port [http].");
             }
 
-            final int portInt = TypeConverters.convert(port, int.class, protocol.equals("https") ? HTTPS : HTTP);
+            final TypeConverter<Integer> converter = TypeUtil.cast(injector.getTypeConverter(Integer.class));
+            final int portInt = converter.convert(port, protocol.equals("https") ? HTTPS : HTTP);
 
             if (Strings.isEmpty(username) || Strings.isEmpty(password)) {
                 LOGGER.error("You must provide a username and password for the CouchDB provider.");
diff --git a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java
index 4e9239d..4477cf9 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java
@@ -16,6 +16,24 @@
  */
 package org.apache.logging.log4j.jdbc.appender;
 
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.StringLayout;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
+import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
+import org.apache.logging.log4j.core.appender.db.ColumnMapping;
+import org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException;
+import org.apache.logging.log4j.core.util.Closer;
+import org.apache.logging.log4j.core.util.Log4jThread;
+import org.apache.logging.log4j.jdbc.convert.DateTypeConverter;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.spi.ThreadContextMap;
+import org.apache.logging.log4j.spi.ThreadContextStack;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.Strings;
+
 import java.io.Serializable;
 import java.io.StringReader;
 import java.sql.Clob;
@@ -38,25 +56,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.StringLayout;
-import org.apache.logging.log4j.core.appender.AppenderLoggingException;
-import org.apache.logging.log4j.core.appender.ManagerFactory;
-import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
-import org.apache.logging.log4j.core.appender.db.ColumnMapping;
-import org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException;
-import org.apache.logging.log4j.core.util.Closer;
-import org.apache.logging.log4j.core.util.Log4jThread;
-import org.apache.logging.log4j.jdbc.convert.DateTypeConverter;
-import org.apache.logging.log4j.message.MapMessage;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
-import org.apache.logging.log4j.spi.ThreadContextMap;
-import org.apache.logging.log4j.spi.ThreadContextStack;
-import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
-import org.apache.logging.log4j.util.Strings;
-
 /**
  * An {@link AbstractDatabaseManager} implementation for relational databases accessed via JDBC.
  */
@@ -759,8 +758,7 @@ public final class JdbcDatabaseManager extends AbstractDatabaseManager {
                         } else if (NClob.class.isAssignableFrom(mapping.getType())) {
                             this.statement.setNClob(j++, new StringReader(layout.toSerializable(event)));
                         } else {
-                            final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(),
-                                    null);
+                            final Object value = mapping.getTypeConverter().convert(layout.toSerializable(event), null);
                             setStatementObject(j++, mapping.getNameKey(), value);
                         }
                     }
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java
index 1de8039..546928f 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoryConverter.java
@@ -18,12 +18,11 @@ package org.apache.logging.log4j.layout.template.json.util;
 
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
 
 /**
  * The default string (i.e., recycler factory spec) to {@link RecyclerFactory} type converter.
  */
-@Plugin(name = "RecyclerFactoryConverter", category = TypeConverters.CATEGORY)
+@Plugin(name = "RecyclerFactoryConverter", category = TypeConverter.CATEGORY)
 public final class RecyclerFactoryConverter implements TypeConverter<RecyclerFactory> {
 
     @Override
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java
index 23bfe05..1a9d235 100644
--- a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/util/RecyclerFactoriesTest.java
@@ -16,12 +16,13 @@
  */
 package org.apache.logging.log4j.layout.template.json.util;
 
-import org.apache.logging.log4j.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.plugins.convert.TypeConverterRegistry;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
 import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
 import org.apache.logging.log4j.core.test.junit.Named;
 import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
-import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.DI;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.assertj.core.api.Assertions;
 import org.jctools.queues.MpmcArrayQueue;
 import org.junit.jupiter.api.Test;
@@ -35,10 +36,10 @@ class RecyclerFactoriesTest {
     @Test
     void test_RecyclerFactoryConverter() throws Exception {
 
+        final Injector injector = DI.createInjector();
+        injector.init();
         // Check if the type converter is registered.
-        final TypeConverter<?> converter = TypeConverterRegistry
-                .getInstance()
-                .findCompatibleConverter(RecyclerFactory.class);
+        final TypeConverter<?> converter = injector.getTypeConverter(RecyclerFactory.class);
         Assertions.assertThat(converter).isNotNull();
 
         // Check dummy recycler factory.
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.java
index 52e05b1..2a5f364 100644
--- a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.java
+++ b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.java
@@ -16,17 +16,23 @@
  */
 package org.apache.logging.log4j.mongodb3;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
+import com.mongodb.MongoClient;
+import com.mongodb.MongoClientOptions;
+import com.mongodb.MongoCredential;
+import com.mongodb.ServerAddress;
+import com.mongodb.WriteConcern;
+import com.mongodb.client.MongoDatabase;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.Core;
 import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
 import org.apache.logging.log4j.core.filter.AbstractFilterable;
+import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
 import org.apache.logging.log4j.plugins.PluginFactory;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.plugins.validation.constraints.Required;
 import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
 import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
@@ -36,12 +42,8 @@ import org.apache.logging.log4j.util.Strings;
 import org.bson.codecs.configuration.CodecRegistries;
 import org.bson.codecs.configuration.CodecRegistry;
 
-import com.mongodb.MongoClient;
-import com.mongodb.MongoClientOptions;
-import com.mongodb.MongoCredential;
-import com.mongodb.ServerAddress;
-import com.mongodb.WriteConcern;
-import com.mongodb.client.MongoDatabase;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
 
 /**
  * The MongoDB implementation of {@link NoSqlProvider} using the MongoDB driver version 3 API.
@@ -125,6 +127,8 @@ public final class MongoDb3Provider implements NoSqlProvider<MongoDb3Connection>
         @PluginBuilderAttribute
         private String writeConcernConstantClassName;
 
+        private Injector injector;
+
         @SuppressWarnings("resource")
         @Override
         public MongoDb3Provider build() {
@@ -180,7 +184,8 @@ public final class MongoDb3Provider implements NoSqlProvider<MongoDb3Connection>
                     mongoCredential = MongoCredential.createCredential(userName, databaseName, password.toCharArray());
                 }
                 try {
-                    final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
+                    final TypeConverter<Integer> converter = TypeUtil.cast(injector.getTypeConverter(Integer.class));
+                    final int portInt = converter.convert(port, DEFAULT_PORT);
                     description += ", server=" + server + ", port=" + portInt;
                     final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
                     // @formatter:off
@@ -286,6 +291,12 @@ public final class MongoDb3Provider implements NoSqlProvider<MongoDb3Connection>
             this.writeConcernConstantClassName = writeConcernConstantClassName;
             return asBuilder();
         }
+
+        @Inject
+        public B setInjector(final Injector injector) {
+            this.injector = injector;
+            return asBuilder();
+        }
     }
 
     private static final int DEFAULT_COLLECTION_SIZE = 536870912;
diff --git a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
index bf4303b..3b68ffa 100644
--- a/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
+++ b/log4j-plugins-test/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
@@ -18,6 +18,8 @@
 package org.apache.logging.log4j.plugins.convert;
 
 import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.di.DI;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -31,7 +33,7 @@ class TypeConverterRegistryTest {
 
     }
 
-    @Plugin(name = "CustomTestClass1Converter1", category = TypeConverters.CATEGORY)
+    @Plugin(name = "CustomTestClass1Converter1", category = TypeConverter.CATEGORY)
     public static final class CustomTestClass1Converter1
             implements TypeConverter<CustomTestClass1> {
 
@@ -43,7 +45,7 @@ class TypeConverterRegistryTest {
     }
 
     @SuppressWarnings("ComparableType")
-    @Plugin(name = "CustomTestClass1Converter2", category = TypeConverters.CATEGORY)
+    @Plugin(name = "CustomTestClass1Converter2", category = TypeConverter.CATEGORY)
     public static final class CustomTestClass1Converter2
             implements TypeConverter<CustomTestClass1>, Comparable<TypeConverter<?>> {
 
@@ -61,9 +63,9 @@ class TypeConverterRegistryTest {
 
     @Test
     public void testMultipleComparableConverters() {
-        final TypeConverter<?> converter = TypeConverterRegistry
-                .getInstance()
-                .findCompatibleConverter(CustomTestClass1.class);
+        final Injector injector = DI.createInjector();
+        injector.init();
+        final TypeConverter<?> converter = injector.getTypeConverter(CustomTestClass1.class);
         assertThat(converter, instanceOf(CustomTestClass1Converter2.class));
     }
 
@@ -73,7 +75,7 @@ class TypeConverterRegistryTest {
 
     }
 
-    @Plugin(name = "CustomTestClass2Converter1", category = TypeConverters.CATEGORY)
+    @Plugin(name = "CustomTestClass2Converter1", category = TypeConverter.CATEGORY)
     public static final class CustomTestClass2Converter1
             implements TypeConverter<CustomTestClass2> {
 
@@ -84,7 +86,7 @@ class TypeConverterRegistryTest {
 
     }
 
-    @Plugin(name = "CustomTestClass2Converter2", category = TypeConverters.CATEGORY)
+    @Plugin(name = "CustomTestClass2Converter2", category = TypeConverter.CATEGORY)
     public static final class CustomTestClass2Converter2
             implements TypeConverter<CustomTestClass2> {
 
@@ -97,9 +99,9 @@ class TypeConverterRegistryTest {
 
     @Test
     public void testMultipleIncomparableConverters() {
-        final TypeConverter<?> converter = TypeConverterRegistry
-                .getInstance()
-                .findCompatibleConverter(CustomTestClass2.class);
+        final Injector injector = DI.createInjector();
+        injector.init();
+        final TypeConverter<?> converter = injector.getTypeConverter(CustomTestClass2.class);
         assertThat(converter, instanceOf(CustomTestClass2Converter1.class));
     }
 
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
index 45e8a16..403ab57 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
@@ -17,16 +17,25 @@
 
 package org.apache.logging.log4j.plugins.convert;
 
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+
 /**
  * Interface for doing automatic String conversion to a specific type.
  *
  * @param <T> Converts Strings into the given type {@code T}.
- * @since 2.1 Moved to the {@code convert} package.
+ * @since 3.0.0 Moved to {@code log4j-plugins}.
  */
 @FunctionalInterface
 public interface TypeConverter<T> {
 
     /**
+     * The {@link Plugin#category() Plugin Category} to use for {@link TypeConverter} plugins.
+     */
+    String CATEGORY = "TypeConverter";
+
+    /**
      * Converts a String to a given type.
      *
      * @param s the String to convert. Cannot be {@code null}.
@@ -34,4 +43,31 @@ public interface TypeConverter<T> {
      * @throws Exception thrown when a conversion error occurs
      */
     T convert(String s) throws Exception;
+
+    default T convert(final String string, final Object defaultValue) {
+        return convert(string, defaultValue, false);
+    }
+
+    default T convert(final String string, final Object defaultValue, final boolean sensitive) {
+        if (string != null) {
+            try {
+                return convert(string);
+            } catch (final Exception e) {
+                StatusLogger.getLogger().warn("Unable to convert string [{}]. Using default value [{}].",
+                        sensitive ? "-redacted-" : string, defaultValue, e);
+            }
+        }
+        if (defaultValue == null) {
+            return null;
+        }
+        if (!(defaultValue instanceof String)) {
+            return TypeUtil.cast(defaultValue);
+        }
+        try {
+            return convert((String) defaultValue);
+        } catch (final Exception e) {
+            StatusLogger.getLogger().debug("Unable to parse default value [{}].", defaultValue, e);
+            return null;
+        }
+    }
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistry.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistry.java
deleted file mode 100644
index 412265c..0000000
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistry.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.plugins.convert;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.plugins.util.PluginType;
-import org.apache.logging.log4j.plugins.util.PluginUtil;
-import org.apache.logging.log4j.plugins.util.TypeUtil;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LazyValue;
-import org.apache.logging.log4j.util.ReflectionUtil;
-
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UnknownFormatConversionException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.function.Supplier;
-
-/**
- * Registry for {@link TypeConverter} plugins.
- *
- * @since 2.1
- */
-public class TypeConverterRegistry {
-
-    private static final Logger LOGGER = StatusLogger.getLogger();
-    private static final Supplier<TypeConverterRegistry> INSTANCE = new LazyValue<>(TypeConverterRegistry::new);
-
-    private final ConcurrentMap<Type, TypeConverter<?>> registry = new ConcurrentHashMap<>();
-
-    /**
-     * Gets the singleton instance of the TypeConverterRegistry.
-     *
-     * @return the singleton instance.
-     */
-    public static TypeConverterRegistry getInstance() {
-        return INSTANCE.get();
-    }
-
-    /**
-     * Finds a {@link TypeConverter} for the given {@link Type}, falling back to an assignment-compatible TypeConverter
-     * if none exist for the given type. That is, if the given Type does not have a TypeConverter, but another Type
-     * which can be assigned to the given Type <em>does</em> have a TypeConverter, then that TypeConverter will be
-     * used and registered.
-     *
-     * @param type the Type to find a TypeConverter for (must not be {@code null}).
-     * @return a TypeConverter for the given Type.
-     * @throws UnknownFormatConversionException if no TypeConverter can be found for the given type.
-     */
-    public TypeConverter<?> findCompatibleConverter(final Type type) {
-        Objects.requireNonNull(type, "No type was provided");
-        final TypeConverter<?> primary = registry.get(type);
-        // cached type converters
-        if (primary != null) {
-            return primary;
-        }
-        // dynamic enum support
-        if (type instanceof Class<?>) {
-            final Class<?> clazz = (Class<?>) type;
-            if (clazz.isEnum()) {
-                @SuppressWarnings({"unchecked","rawtypes"})
-                final EnumConverter<? extends Enum> converter = new EnumConverter(clazz.asSubclass(Enum.class));
-                synchronized (INSTANCE) {
-                    return registerConverter(type, converter);
-                }
-            }
-        }
-        // look for compatible converters
-        for (final Map.Entry<Type, TypeConverter<?>> entry : registry.entrySet()) {
-            final Type key = entry.getKey();
-            if (TypeUtil.isAssignable(type, key)) {
-                LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
-                final TypeConverter<?> value = entry.getValue();
-                synchronized (INSTANCE) {
-                    return registerConverter(type, value);
-                }
-            }
-        }
-        throw new UnknownFormatConversionException(type.toString());
-    }
-
-    private TypeConverterRegistry() {
-        LOGGER.trace("TypeConverterRegistry initializing.");
-        loadKnownTypeConverters(PluginUtil.collectPluginsByCategory(TypeConverters.CATEGORY).values());
-        registerPrimitiveTypes();
-    }
-
-    private void loadKnownTypeConverters(final Collection<PluginType<?>> knownTypes) {
-        for (final PluginType<?> knownType : knownTypes) {
-            final Class<?> clazz = knownType.getPluginClass();
-            if (TypeConverter.class.isAssignableFrom(clazz)) {
-                @SuppressWarnings("rawtypes") final Class<? extends TypeConverter> pluginClass =
-                        clazz.asSubclass(TypeConverter.class);
-                final Type conversionType = getTypeConverterSupportedType(pluginClass);
-                final TypeConverter<?> converter = ReflectionUtil.instantiate(pluginClass);
-                registerConverter(conversionType, converter);
-            }
-        }
-    }
-
-    /**
-     * Attempts to register the given converter and returns the effective
-     * converter associated with the given type.
-     * <p>
-     * Registration will fail if there already exists a converter for the given
-     * type and neither the existing, nor the provided converter extends from {@link Comparable}.
-     */
-    private TypeConverter<?> registerConverter(
-            final Type conversionType,
-            final TypeConverter<?> converter) {
-        final TypeConverter<?> conflictingConverter = registry.get(conversionType);
-        if (conflictingConverter != null) {
-            final boolean overridable;
-            if (converter instanceof Comparable) {
-                @SuppressWarnings("unchecked")
-                final Comparable<TypeConverter<?>> comparableConverter =
-                        (Comparable<TypeConverter<?>>) converter;
-                overridable = comparableConverter.compareTo(conflictingConverter) < 0;
-            } else if (conflictingConverter instanceof Comparable) {
-                @SuppressWarnings("unchecked")
-                final Comparable<TypeConverter<?>> comparableConflictingConverter =
-                        (Comparable<TypeConverter<?>>) conflictingConverter;
-                overridable = comparableConflictingConverter.compareTo(converter) > 0;
-            } else {
-                overridable = false;
-            }
-            if (overridable) {
-                LOGGER.debug(
-                        "Replacing TypeConverter [{}] for type [{}] with [{}] after comparison.",
-                        conflictingConverter, conversionType, converter);
-                registry.put(conversionType, converter);
-                return converter;
-            } else {
-                LOGGER.warn(
-                        "Ignoring TypeConverter [{}] for type [{}] that conflicts with [{}], since they are not comparable.",
-                        converter, conversionType, conflictingConverter);
-                return conflictingConverter;
-            }
-        } else {
-            registry.put(conversionType, converter);
-            return converter;
-        }
-    }
-
-    private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class<? extends TypeConverter> typeConverterClass) {
-        for (final Type type : typeConverterClass.getGenericInterfaces()) {
-            if (type instanceof ParameterizedType) {
-                final ParameterizedType pType = (ParameterizedType) type;
-                if (TypeConverter.class.equals(pType.getRawType())) {
-                    // TypeConverter<T> has only one type argument (T), so return that
-                    return pType.getActualTypeArguments()[0];
-                }
-            }
-        }
-        return Void.TYPE;
-    }
-
-    private void registerPrimitiveTypes() {
-        registerConverter(Boolean.class, Boolean::valueOf);
-        registerTypeAlias(Boolean.class, Boolean.TYPE);
-        registerConverter(Byte.class, Byte::valueOf);
-        registerTypeAlias(Byte.class, Byte.TYPE);
-        registerConverter(Character.class, s -> {
-            if (s.length() != 1) {
-                throw new IllegalArgumentException("Character string must be of length 1: " + s);
-            }
-            return s.toCharArray()[0];
-        });
-        registerTypeAlias(Character.class, Character.TYPE);
-        registerConverter(Double.class, Double::valueOf);
-        registerTypeAlias(Double.class, Double.TYPE);
-        registerConverter(Float.class, Float::valueOf);
-        registerTypeAlias(Float.class, Float.TYPE);
-        registerConverter(Integer.class, Integer::valueOf);
-        registerTypeAlias(Integer.class, Integer.TYPE);
-        registerConverter(Long.class, Long::valueOf);
-        registerTypeAlias(Long.class, Long.TYPE);
-        registerConverter(Short.class, Short::valueOf);
-        registerTypeAlias(Short.class, Short.TYPE);
-        registerConverter(String.class, s -> s);
-    }
-
-    private void registerTypeAlias(final Type knownType, final Type aliasType) {
-        TypeConverter<?> converter = registry.get(knownType);
-        if (converter != null) {
-            registry.putIfAbsent(aliasType, converter);
-        } else {
-            LOGGER.error("Cannot locate converter for {}", knownType);
-        }
-    }
-
-}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverters.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverters.java
deleted file mode 100644
index 0ce0717..0000000
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverters.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.plugins.convert;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.plugins.Plugin;
-import org.apache.logging.log4j.plugins.util.TypeUtil;
-import org.apache.logging.log4j.status.StatusLogger;
-
-import java.lang.reflect.Type;
-
-/**
- * Collection of basic TypeConverter implementations. May be used to register additional TypeConverters or find
- * registered TypeConverters.
- *
- * @since 2.1 Moved to the {@code convert} package.
- */
-public final class TypeConverters {
-
-    /**
-     * The {@link Plugin#category() Plugin Category} to use for {@link TypeConverter} plugins.
-     *
-     * @since 2.1
-     */
-    public static final String CATEGORY = "TypeConverter";
-
-    /**
-     * Converts a String to a given class if a TypeConverter is available for that class. Falls back to the provided
-     * default value if the conversion is unsuccessful. However, if the default value is <em>also</em> invalid, then
-     * {@code null} is returned (along with a nasty status log message).
-     *
-     * @param s
-     *        the string to convert
-     * @param clazz
-     *        the class to try to convert the string to
-     * @param defaultValue
-     *        the fallback object to use if the conversion is unsuccessful
-     * @param <T> The type of the clazz parameter.
-     * @return the converted object which may be {@code null} if the string is invalid for the given type
-     * @throws NullPointerException
-     *         if {@code clazz} is {@code null}
-     * @throws IllegalArgumentException
-     *         if no TypeConverter exists for the given class
-     */
-    public static <T> T convert(final String s, final Class<? extends T> clazz, final Object defaultValue) {
-        return convert(s, clazz, defaultValue, false);
-    }
-
-    public static <T> T convert(final String s, final Type targetType, final Object defaultValue, final boolean sensitive) {
-        final TypeConverter<T> converter =
-                TypeUtil.cast(TypeConverterRegistry.getInstance().findCompatibleConverter(targetType));
-        if (s == null) {
-            return parseDefaultValue(converter, defaultValue);
-        }
-        try {
-            return converter.convert(s);
-        } catch (final Exception e) {
-            LOGGER.warn("Error while converting string [{}] to type [{}]. Using default value [{}].",
-                    sensitive ? "-redacted-" : s, targetType, defaultValue, e);
-            return parseDefaultValue(converter, defaultValue);
-        }
-    }
-
-    private static <T> T parseDefaultValue(final TypeConverter<T> converter, final Object defaultValue) {
-        if (defaultValue == null) {
-            return null;
-        }
-        if (!(defaultValue instanceof String)) {
-            return TypeUtil.cast(defaultValue);
-        }
-        try {
-            return converter.convert((String) defaultValue);
-        } catch (final Exception e) {
-            LOGGER.debug("Can't parse default value [{}] for type [{}].", defaultValue, converter.getClass(), e);
-            return null;
-        }
-    }
-
-    private static final Logger LOGGER = StatusLogger.getLogger();
-
-}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
index 1937234..6d62cce 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/DefaultInjector.java
@@ -25,6 +25,7 @@ import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.QualifierType;
 import org.apache.logging.log4j.plugins.ScopeType;
 import org.apache.logging.log4j.plugins.Singleton;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
 import org.apache.logging.log4j.plugins.util.AnnotationUtil;
@@ -36,6 +37,7 @@ import org.apache.logging.log4j.plugins.validation.ConstraintValidationException
 import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
 import org.apache.logging.log4j.plugins.visit.NodeVisitor;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.EnglishEnums;
 import org.apache.logging.log4j.util.LazyValue;
 import org.apache.logging.log4j.util.ServiceRegistry;
 import org.apache.logging.log4j.util.StringBuilders;
@@ -63,6 +65,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
+import java.util.UnknownFormatConversionException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -73,6 +76,7 @@ class DefaultInjector implements Injector {
 
     private final BindingMap bindingMap;
     private final Map<Class<? extends Annotation>, Scope> scopes = new ConcurrentHashMap<>();
+    private final Map<Type, TypeConverter<?>> typeConverters = new ConcurrentHashMap<>();
     private ReflectionAccessor accessor = object -> object.setAccessible(true);
 
     DefaultInjector() {
@@ -84,6 +88,7 @@ class DefaultInjector implements Injector {
     DefaultInjector(final DefaultInjector original) {
         bindingMap = new BindingMap(original.bindingMap);
         scopes.putAll(original.scopes);
+        typeConverters.putAll(original.typeConverters);
         accessor = original.accessor;
     }
 
@@ -112,6 +117,40 @@ class DefaultInjector implements Injector {
     }
 
     @Override
+    public TypeConverter<?> getTypeConverter(final Type type) {
+        if (typeConverters.isEmpty()) {
+            synchronized (typeConverters) {
+                if (typeConverters.isEmpty()) {
+                    LOGGER.trace("Initializing type converters");
+                    initializeTypeConverters();
+                }
+            }
+        }
+        final TypeConverter<?> primary = typeConverters.get(type);
+        // cached type converters
+        if (primary != null) {
+            return primary;
+        }
+        // dynamic enum support
+        if (type instanceof Class<?>) {
+            final Class<?> clazz = (Class<?>) type;
+            if (clazz.isEnum()) {
+                return registerTypeConverter(type, s -> EnglishEnums.valueOf(clazz.asSubclass(Enum.class), s));
+            }
+        }
+        // look for compatible converters
+        for (final Map.Entry<Type, TypeConverter<?>> entry : typeConverters.entrySet()) {
+            final Type key = entry.getKey();
+            if (TypeUtil.isAssignable(type, key)) {
+                LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
+                final TypeConverter<?> value = entry.getValue();
+                return registerTypeConverter(type, value);
+            }
+        }
+        throw new UnknownFormatConversionException(type.toString());
+    }
+
+    @Override
     public void injectMembers(final Object instance) {
         injectMembers(Key.forClass(instance.getClass()), null, instance, Set.of(), null);
     }
@@ -253,6 +292,83 @@ class DefaultInjector implements Injector {
         }
     }
 
+    private void initializeTypeConverters() {
+        final PluginManager manager = getInstance(new @Named(TypeConverter.CATEGORY) Key<>() {});
+        manager.collectPlugins();
+        for (final PluginType<?> knownType : manager.getPlugins().values()) {
+            final Class<?> pluginClass = knownType.getPluginClass();
+            final Type type = getTypeConverterSupportedType(pluginClass);
+            final TypeConverter<?> converter = getInstance(pluginClass.asSubclass(TypeConverter.class));
+            registerTypeConverter(type, converter);
+        }
+        registerTypeConverter(Boolean.class, Boolean::valueOf);
+        registerTypeAlias(Boolean.class, Boolean.TYPE);
+        registerTypeConverter(Byte.class, Byte::valueOf);
+        registerTypeAlias(Byte.class, Byte.TYPE);
+        registerTypeConverter(Character.class, s -> {
+            if (s.length() != 1) {
+                throw new IllegalArgumentException("Character string must be of length 1: " + s);
+            }
+            return s.toCharArray()[0];
+        });
+        registerTypeAlias(Character.class, Character.TYPE);
+        registerTypeConverter(Double.class, Double::valueOf);
+        registerTypeAlias(Double.class, Double.TYPE);
+        registerTypeConverter(Float.class, Float::valueOf);
+        registerTypeAlias(Float.class, Float.TYPE);
+        registerTypeConverter(Integer.class, Integer::valueOf);
+        registerTypeAlias(Integer.class, Integer.TYPE);
+        registerTypeConverter(Long.class, Long::valueOf);
+        registerTypeAlias(Long.class, Long.TYPE);
+        registerTypeConverter(Short.class, Short::valueOf);
+        registerTypeAlias(Short.class, Short.TYPE);
+        registerTypeConverter(String.class, s -> s);
+    }
+
+    private TypeConverter<?> registerTypeConverter(final Type type, final TypeConverter<?> converter) {
+        final TypeConverter<?> conflictingConverter = typeConverters.get(type);
+        if (conflictingConverter != null) {
+            final boolean overridable;
+            if (converter instanceof Comparable) {
+                @SuppressWarnings("unchecked")
+                final Comparable<TypeConverter<?>> comparableConverter =
+                        (Comparable<TypeConverter<?>>) converter;
+                overridable = comparableConverter.compareTo(conflictingConverter) < 0;
+            } else if (conflictingConverter instanceof Comparable) {
+                @SuppressWarnings("unchecked")
+                final Comparable<TypeConverter<?>> comparableConflictingConverter =
+                        (Comparable<TypeConverter<?>>) conflictingConverter;
+                overridable = comparableConflictingConverter.compareTo(converter) > 0;
+            } else {
+                overridable = false;
+            }
+            if (overridable) {
+                LOGGER.debug(
+                        "Replacing TypeConverter [{}] for type [{}] with [{}] after comparison.",
+                        conflictingConverter, type, converter);
+                typeConverters.put(type, converter);
+                return converter;
+            } else {
+                LOGGER.warn(
+                        "Ignoring TypeConverter [{}] for type [{}] that conflicts with [{}], since they are not comparable.",
+                        converter, type, conflictingConverter);
+                return conflictingConverter;
+            }
+        } else {
+            typeConverters.put(type, converter);
+            return converter;
+        }
+    }
+
+    private void registerTypeAlias(final Type knownType, final Type aliasType) {
+        final TypeConverter<?> converter = typeConverters.get(knownType);
+        if (converter != null) {
+            typeConverters.put(aliasType, converter);
+        } else {
+            LOGGER.error("Cannot locate type converter for {}", knownType);
+        }
+    }
+
     private void injectMembers(
             final Key<?> key, final Node node, final Object instance, final Set<Key<?>> chain, final StringBuilder debugLog) {
         injectFields(key.getRawType(), node, instance, debugLog);
@@ -495,6 +611,18 @@ class DefaultInjector implements Injector {
         ((ConstraintValidator) validator).initialize(annotation);
     }
 
+    private static Type getTypeConverterSupportedType(final Class<?> clazz) {
+        for (final Type type : clazz.getGenericInterfaces()) {
+            if (type instanceof ParameterizedType) {
+                final ParameterizedType parameterizedType = (ParameterizedType) type;
+                if (parameterizedType.getRawType() == TypeConverter.class) {
+                    return parameterizedType.getActualTypeArguments()[0];
+                }
+            }
+        }
+        return Void.TYPE;
+    }
+
     private static void verifyAttributesConsumed(final Node node) {
         final Map<String, String> attrs = node.getAttributes();
         if (!attrs.isEmpty()) {
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
index b51d845..24a0ee9 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/di/Injector.java
@@ -19,8 +19,10 @@ package org.apache.logging.log4j.plugins.di;
 
 import org.apache.logging.log4j.plugins.FactoryType;
 import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
 
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
 import java.util.function.Supplier;
 
 /**
@@ -87,6 +89,15 @@ public interface Injector {
     }
 
     /**
+     * Gets a TypeConverter for the provided type.
+     *
+     * @param type type to get a converter for
+     * @return a TypeConverter for the provided type
+     * @throws java.util.UnknownFormatConversionException if no converter can be found for the provided type
+     */
+    TypeConverter<?> getTypeConverter(final Type type);
+
+    /**
      * Injects dependencies into the members of the provided instance. Injectable fields are set, then injectable methods are
      * invoked (first those with parameters, then those without parameters).
      *
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.java
index 27e97f0..a65fecc 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.java
@@ -17,9 +17,12 @@
 package org.apache.logging.log4j.plugins.validation.validators;
 
 import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Inject;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
 import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
 import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
 import org.apache.logging.log4j.status.StatusLogger;
 
 /**
@@ -31,8 +34,14 @@ public class ValidPortValidator implements ConstraintValidator<ValidPort> {
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
+    private final TypeConverter<Integer> converter;
     private ValidPort annotation;
 
+    @Inject
+    public ValidPortValidator(final Injector injector) {
+        converter = TypeUtil.cast(injector.getTypeConverter(Integer.class));
+    }
+
     @Override
     public void initialize(final ValidPort annotation) {
         this.annotation = annotation;
@@ -41,9 +50,9 @@ public class ValidPortValidator implements ConstraintValidator<ValidPort> {
     @Override
     public boolean isValid(final String name, final Object value) {
         if (value instanceof CharSequence) {
-            return isValid(name, TypeConverters.convert(value.toString(), Integer.class, -1));
+            return isValid(name, converter.convert(value.toString(), -1));
         }
-        if (!Integer.class.isInstance(value)) {
+        if (!(value instanceof Integer)) {
             LOGGER.error(annotation.message());
             return false;
         }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginAttributeVisitor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginAttributeVisitor.java
index 58cae4f..600f684 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginAttributeVisitor.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginAttributeVisitor.java
@@ -21,7 +21,8 @@ import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.PluginAttribute;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Keys;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
@@ -59,10 +60,14 @@ public class PluginAttributeVisitor implements NodeVisitor {
     );
 
     private final Function<String, String> stringSubstitutionStrategy;
+    private final Injector injector;
 
     @Inject
-    public PluginAttributeVisitor(@Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy) {
+    public PluginAttributeVisitor(
+            @Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy,
+            final Injector injector) {
         this.stringSubstitutionStrategy = stringSubstitutionStrategy;
+        this.injector = injector;
     }
 
     @Override
@@ -72,8 +77,9 @@ public class PluginAttributeVisitor implements NodeVisitor {
         final PluginAttribute annotation = field.getAnnotation(PluginAttribute.class);
         final boolean sensitive = annotation.sensitive();
         final Type targetType = field.getGenericType();
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final Object value = node.removeMatchingAttribute(name, aliases)
-                .map(stringSubstitutionStrategy.andThen(s -> TypeConverters.convert(s, targetType, null, sensitive)))
+                .map(stringSubstitutionStrategy.andThen(s -> (Object) converter.convert(s, null, sensitive)))
                 .orElseGet(() -> getDefaultValue(targetType, annotation));
         StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", ");
         return value;
@@ -84,10 +90,11 @@ public class PluginAttributeVisitor implements NodeVisitor {
         final String name = AnnotatedElementNameProvider.getName(parameter);
         final Collection<String> aliases = AnnotatedElementAliasesProvider.getAliases(parameter);
         final Type targetType = parameter.getParameterizedType();
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final PluginAttribute annotation = parameter.getAnnotation(PluginAttribute.class);
         final boolean sensitive = annotation.sensitive();
         final Object value = node.removeMatchingAttribute(name, aliases)
-                .map(stringSubstitutionStrategy.andThen(s -> TypeConverters.convert(s, targetType, null, sensitive)))
+                .map(stringSubstitutionStrategy.andThen(s -> (Object) converter.convert(s, null, sensitive)))
                 .orElseGet(() -> getDefaultValue(targetType, annotation));
         StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", ");
         return value;
@@ -98,7 +105,8 @@ public class PluginAttributeVisitor implements NodeVisitor {
         if (extractor != null) {
             return extractor.apply(annotation);
         }
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final var value = stringSubstitutionStrategy.apply(annotation.defaultString());
-        return Strings.isEmpty(value) ? null : TypeConverters.convert(value, targetType, null, false);
+        return Strings.isEmpty(value) ? null : converter.convert(value, null);
     }
 }
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginBuilderAttributeVisitor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginBuilderAttributeVisitor.java
index 9a381f2..19f0c3e 100644
--- a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginBuilderAttributeVisitor.java
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/visit/PluginBuilderAttributeVisitor.java
@@ -21,7 +21,8 @@ import org.apache.logging.log4j.plugins.Inject;
 import org.apache.logging.log4j.plugins.Named;
 import org.apache.logging.log4j.plugins.Node;
 import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.di.Injector;
 import org.apache.logging.log4j.plugins.di.Keys;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementAliasesProvider;
 import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
@@ -36,10 +37,14 @@ import java.util.function.Function;
 
 public class PluginBuilderAttributeVisitor implements NodeVisitor {
     private final Function<String, String> stringSubstitutionStrategy;
+    private final Injector injector;
 
     @Inject
-    public PluginBuilderAttributeVisitor(@Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy) {
+    public PluginBuilderAttributeVisitor(
+            @Named(Keys.SUBSTITUTOR_NAME) final Function<String, String> stringSubstitutionStrategy,
+            final Injector injector) {
         this.stringSubstitutionStrategy = stringSubstitutionStrategy;
+        this.injector = injector;
     }
 
     protected boolean isSensitive(final AnnotatedElement element) {
@@ -51,9 +56,10 @@ public class PluginBuilderAttributeVisitor implements NodeVisitor {
         final String name = AnnotatedElementNameProvider.getName(field);
         final Collection<String> aliases = AnnotatedElementAliasesProvider.getAliases(field);
         final Type targetType = field.getGenericType();
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final boolean sensitive = isSensitive(field);
         final Object value = node.removeMatchingAttribute(name, aliases)
-                .map(stringSubstitutionStrategy.andThen(s -> TypeConverters.convert(s, targetType, null, sensitive)))
+                .map(stringSubstitutionStrategy.andThen(s -> converter.convert(s, null, sensitive)))
                 .orElse(null);
         StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", ");
         return value;
@@ -64,9 +70,10 @@ public class PluginBuilderAttributeVisitor implements NodeVisitor {
         final String name = AnnotatedElementNameProvider.getName(parameter);
         final Collection<String> aliases = AnnotatedElementAliasesProvider.getAliases(parameter);
         final Type targetType = parameter.getParameterizedType();
+        final TypeConverter<?> converter = injector.getTypeConverter(targetType);
         final boolean sensitive = isSensitive(parameter);
         final Object value = node.removeMatchingAttribute(name, aliases)
-                .map(stringSubstitutionStrategy.andThen(s -> TypeConverters.convert(s, targetType, null, sensitive)))
+                .map(stringSubstitutionStrategy.andThen(s -> converter.convert(s, null, sensitive)))
                 .orElse(null);
         StringBuilders.appendKeyDqValueWithJoiner(debugLog, name, sensitive ? "(***)" : value, ", ");
         return value;
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 8e86a8c..9e1b10e 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -1788,9 +1788,8 @@ package com.acme.logging.log4j.layout.template.json;
 
 import org.apache.logging.log4j.plugins.Plugin;
 import org.apache.logging.log4j.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.plugins.convert.TypeConverters;
 
-@Plugin(name = "AcmeRecyclerFactoryConverter", category = TypeConverters.CATEGORY)
+@Plugin(name = "AcmeRecyclerFactoryConverter", category = TypeConverter.CATEGORY)
 public final class AcmeRecyclerFactoryConverter
         implements TypeConverter<RecyclerFactory>, Comparable<TypeConverter<?>> {