From 230553f636007fe79f01e73112a682884c208519 Mon Sep 17 00:00:00 2001
From: Jeff Thomas <jeffrey.thomas@t-systems.com>
Date: Mon, 10 Feb 2025 18:04:12 +0100
Subject: [PATCH 1/2] Improve extensibility of
 DefaultConfigurationBuilder/BuiltConfiguration (#3441)

---
 .../builder/CustomBuiltConfigurationTest.java | 360 ++++++++
 .../builder/api/ConfigurationBuilder.java     | 125 ++-
 .../core/config/builder/api/package-info.java |   2 +-
 .../builder/impl/BuiltConfiguration.java      | 331 +++++--
 .../impl/DefaultConfigurationBuilder.java     | 858 +++++++++++++-----
 .../config/builder/impl/package-info.java     |   2 +-
 .../core/config/properties/package-info.java  |   2 +-
 ...lt_configuration_builder_extensibility.xml |  11 +
 8 files changed, 1364 insertions(+), 327 deletions(-)
 create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java
 create mode 100644 src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml

diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java
new file mode 100644
index 00000000000..246ffe2c432
--- /dev/null
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/builder/CustomBuiltConfigurationTest.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.config.builder;
+
+import static org.assertj.core.api.Assertions.assertThatCollection;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.appender.mom.kafka.KafkaAppender;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.CustomLevelConfig;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
+import org.apache.logging.log4j.core.config.builder.api.Component;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
+import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder;
+import org.apache.logging.log4j.core.filter.ThresholdFilter;
+import org.junit.jupiter.api.Test;
+
+class CustomBuiltConfigurationTest {
+
+    private static final String CONFIG_NAME = "FooBar Configuration";
+
+    private static final FooBar FOOBAR = new FooBar("wingding");
+
+    /** Test that the build configuration contains the intended attributes. */
+    @Test
+    void testCustomBuiltConfiguration_Attributes() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+            assertEquals(CONFIG_NAME, config.getName());
+            assertEquals(10, config.getMonitorInterval());
+            assertEquals(5000, config.getShutdownTimeoutMillis());
+            assertThatCollection(config.getPluginPackages()).containsExactlyInAnyOrder("foo", "bar");
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /** Test that the custom constructor of the custom configuration was called and the test object is accessible. */
+    @Test
+    void testCustomBuiltConfiguration_CustomObject() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+
+            final FooBar fb = config.getFooBar();
+            assertNotNull(fb);
+            assertEquals(FOOBAR, fb);
+            assertEquals(FOOBAR.getValue(), fb.getValue());
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /** Test that the build configuration contains the intended appenders. */
+    @Test
+    void testCustomBuiltConfiguration_Appenders() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+            assertThatCollection(config.getAppenders().keySet()).containsExactlyInAnyOrder("Stdout", "Kafka");
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /** Test that the build configuration contains the custom levels. */
+    @Test
+    void testCustomBuiltConfiguration_CustomLevels() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+            assertThatCollection(config.getCustomLevels().stream()
+                            .map(CustomLevelConfig::getLevelName)
+                            .collect(Collectors.toList()))
+                    .containsExactlyInAnyOrder("Panic");
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /** Test that the build configuration contains the intended filter. */
+    @Test
+    void testCustomBuiltConfiguration_Filter() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+            Filter filter = config.getFilter();
+            assertNotNull(filter);
+            assertInstanceOf(ThresholdFilter.class, filter);
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /** Test that the build configuration contains the intended loggers. */
+    @Test
+    void testCustomBuiltConfiguration_Loggers() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+            assertThatCollection(config.getLoggers().keySet())
+                    .containsExactlyInAnyOrder("", "org.apache.logging.log4j", "org.apache.logging.log4j.core");
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /** Test that the build configuration correctly registers and resolves properties. */
+    @Test
+    void testCustomBuiltConfiguration_PropertyResolution() {
+
+        final LoggerContext loggerContext = new LoggerContext("CustomBuiltConfigurationTest");
+        final FoobarConfigurationBuilder builder = createTestBuilder(loggerContext);
+        final FooBarConfiguration config = builder.build(false);
+
+        try {
+
+            // build the configuration and set it in the context to start the configuration
+            loggerContext.setConfiguration(config);
+
+            assertNotNull(config);
+            assertEquals("Wing", config.getConfigurationStrSubstitutor().replace("${P1}"));
+            assertEquals("WingDing", config.getConfigurationStrSubstitutor().replace("${P2}"));
+
+            Appender kafkaAppender = config.getAppender("Kafka");
+            assertNotNull(kafkaAppender);
+            assertInstanceOf(KafkaAppender.class, kafkaAppender);
+            final Property p2Property = Arrays.stream(((KafkaAppender) kafkaAppender).getPropertyArray())
+                    .collect(Collectors.toMap(Property::getName, Function.identity()))
+                    .get("P2");
+            assertNotNull(p2Property);
+            assertEquals("WingDing", p2Property.getValue());
+
+        } finally {
+
+            loggerContext.stop();
+        }
+    }
+
+    /**
+     * Creates, preconfigures and returns a test builder instance.
+     * @param loggerContext the logger context to use
+     * @return the created configuration builder
+     */
+    private FoobarConfigurationBuilder createTestBuilder(final LoggerContext loggerContext) {
+        final FoobarConfigurationBuilder builder = new FoobarConfigurationBuilder();
+        builder.setLoggerContext(loggerContext);
+        addTestFixtures(builder);
+        return builder;
+    }
+
+    /**
+     * Populates the given configuration-builder.
+     * @param builder the builder
+     */
+    private void addTestFixtures(final ConfigurationBuilder<? extends BuiltConfiguration> builder) {
+        builder.setConfigurationName(CONFIG_NAME);
+        builder.setStatusLevel(Level.ERROR);
+        builder.setMonitorInterval(10);
+        builder.setShutdownTimeout(5000, TimeUnit.MILLISECONDS);
+        builder.add(builder.newScriptFile("target/test-classes/scripts/filter.groovy")
+                .addIsWatched(true));
+        builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
+                .addAttribute("level", Level.DEBUG));
+
+        final AppenderComponentBuilder appenderBuilder =
+                builder.newAppender("Stdout", "CONSOLE").addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
+        appenderBuilder.add(
+                builder.newLayout("PatternLayout").addAttribute("pattern", "%d [%t] %-5level: %msg%n%throwable"));
+        appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
+                .addAttribute("marker", "FLOW"));
+        builder.add(appenderBuilder);
+
+        final AppenderComponentBuilder appenderBuilder2 =
+                builder.newAppender("Kafka", "Kafka").addAttribute("topic", "my-topic");
+        appenderBuilder2.addComponent(builder.newProperty("bootstrap.servers", "localhost:9092"));
+        appenderBuilder2.addComponent(builder.newProperty("P2", "${P2}"));
+        appenderBuilder2.add(builder.newLayout("GelfLayout")
+                .addAttribute("host", "my-host")
+                .addComponent(builder.newKeyValuePair("extraField", "extraValue")));
+        builder.add(appenderBuilder2);
+
+        builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG, true)
+                .add(builder.newAppenderRef("Stdout"))
+                .addAttribute("additivity", false));
+        builder.add(builder.newLogger("org.apache.logging.log4j.core").add(builder.newAppenderRef("Stdout")));
+        builder.add(builder.newRootLogger(Level.ERROR).add(builder.newAppenderRef("Stdout")));
+
+        builder.addProperty("P1", "Wing");
+        builder.addProperty("P2", "${P1}Ding");
+        builder.add(builder.newCustomLevel("Panic", 17));
+        builder.setPackages("foo,bar");
+    }
+
+    //
+    // Test implementations
+    //
+
+    /** A custom {@link DefaultConfigurationBuilder} implementation that generates a {@link FooBarConfiguration}. */
+    public static class FoobarConfigurationBuilder extends DefaultConfigurationBuilder<FooBarConfiguration> {
+
+        public FoobarConfigurationBuilder() {
+            super(FooBarConfiguration.class);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected FooBarConfiguration createNewConfigurationInstance(Class<FooBarConfiguration> configurationClass) {
+            Objects.requireNonNull(configurationClass, "The 'configurationClass' argument must not be null.");
+            try {
+                final Constructor<FooBarConfiguration> constructor = FooBarConfiguration.class.getConstructor(
+                        LoggerContext.class, ConfigurationSource.class, Component.class, FooBar.class);
+                return constructor.newInstance(
+                        getLoggerContext().orElse(null),
+                        getConfigurationSource().orElse(null),
+                        getRootComponent(),
+                        FOOBAR);
+            } catch (final Exception ex) {
+                throw new IllegalStateException(
+                        "Configuration class '" + configurationClass.getName() + "' cannot be instantiated.", ex);
+            }
+        }
+    }
+
+    /**
+     * A custom {@link BuiltConfiguration} implementation with a custom constructor that takes
+     * an additional {@code FooBar} argument.
+     */
+    public static class FooBarConfiguration extends BuiltConfiguration {
+
+        private int monitorInterval;
+
+        private final FooBar fooBar;
+
+        public FooBarConfiguration(
+                LoggerContext loggerContext, ConfigurationSource source, Component rootComponent, FooBar fooBar) {
+            super(loggerContext, source, rootComponent);
+            this.fooBar = Objects.requireNonNull(fooBar, "fooBar");
+        }
+
+        public FooBar getFooBar() {
+            return fooBar;
+        }
+
+        public int getMonitorInterval() {
+            return this.monitorInterval;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setMonitorInterval(int seconds) {
+            super.setMonitorInterval(seconds);
+            this.monitorInterval = seconds;
+        }
+    }
+
+    /** Test object used by custom configuration-builder and configuration. */
+    public static class FooBar {
+
+        private final String value;
+
+        public FooBar(final String value) {
+            this.value = value;
+        }
+
+        public String getValue() {
+            return this.value;
+        }
+    }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
index 3b9fc489614..b8c174f39e5 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
@@ -23,34 +23,43 @@
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationException;
 import org.apache.logging.log4j.core.config.ConfigurationSource;
 import org.apache.logging.log4j.core.util.Builder;
+import org.osgi.annotation.versioning.ProviderType;
 
 /**
  * Interface for building logging configurations.
  * @param <T> The Configuration type created by this builder.
  * @since 2.4
  */
+@ProviderType
 public interface ConfigurationBuilder<T extends Configuration> extends Builder<T> {
 
     /**
      * Adds a ScriptComponent.
-     * @param builder The ScriptComponentBuilder with all of its attributes and sub components set.
+     * @param builder The ScriptComponentBuilder with all of its attributes and subcomponents set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(ScriptComponentBuilder builder);
 
     /**
      * Adds a ScriptFileComponent.
-     * @param builder The ScriptFileComponentBuilder with all of its attributes and sub components set.
+     * @param builder The ScriptFileComponentBuilder with all of its attributes and subcomponents set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(ScriptFileComponentBuilder builder);
 
     /**
      * Adds an AppenderComponent.
-     * @param builder The AppenderComponentBuilder with all of its attributes and sub components set.
+     * @param builder The AppenderComponentBuilder with all of its attributes and subcomponents set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(AppenderComponentBuilder builder);
 
@@ -58,38 +67,61 @@ public interface ConfigurationBuilder<T extends Configuration> extends Builder<T
      * Adds a CustomLevel component.
      * @param builder The CustomLevelComponentBuilder with all of its attributes set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(CustomLevelComponentBuilder builder);
 
     /**
      * Adds a Filter component.
-     * @param builder the FilterComponentBuilder with all of its attributes and sub components set.
+     * @param builder the FilterComponentBuilder with all of its attributes and subcomponents set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(FilterComponentBuilder builder);
 
     /**
      * Adds a Logger component.
-     * @param builder The LoggerComponentBuilder with all of its attributes and sub components set.
+     * @param builder The LoggerComponentBuilder with all of its attributes and subcomponents set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(LoggerComponentBuilder builder);
 
     /**
      * Adds the root Logger component.
-     * @param builder The RootLoggerComponentBuilder with all of its attributes and sub components set.
+     * @param builder The RootLoggerComponentBuilder with all of its attributes and subcomponents set.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> add(RootLoggerComponentBuilder builder);
 
     /**
      * Adds a Property key and value.
+     * <p>
+     *     This is a convenience method which creates, configures and immediately adds
+     *     a {@code PropertyComponentBuilder}.
+     * </p>
      * @param key The property key.
      * @param value The property value.
      * @return this builder instance.
+     * @throws ConfigurationException if an error occurs while building the created {@code PropertyComponentBuilder}
+     * @throws NullPointerException if the builder argument is null
      */
     ConfigurationBuilder<T> addProperty(String key, String value);
 
+    /**
+     * Adds a Property component.
+     * @param builder the PropertyComponentBuilder with all of its attributes and subcomponents set
+     * @return this builder instance
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if the builder argument is null
+     */
+    ConfigurationBuilder<T> addProperty(PropertyComponentBuilder builder);
+
     /**
      * Returns a builder for creating Async Loggers.
      * @param name The name of the Logger.
@@ -392,6 +424,9 @@ public interface ConfigurationBuilder<T extends Configuration> extends Builder<T
 
     /**
      * Set the Advertiser Plugin name.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
      * @param advertiser The Advertiser Plugin name.
      * @return this builder instance.
      */
@@ -399,13 +434,19 @@ public interface ConfigurationBuilder<T extends Configuration> extends Builder<T
 
     /**
      * Sets the name of the configuration.
-     * @param name the name of the {@link Configuration}. By default is {@code "Constructed"}.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
+     * @param name the name of the {@link Configuration}. By default, the value is {@code "Built"}.
      * @return this builder instance.
      */
     ConfigurationBuilder<T> setConfigurationName(String name);
 
     /**
      * Sets the configuration source, if one exists.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
      * @param configurationSource the ConfigurationSource.
      * @return this builder instance.
      */
@@ -413,13 +454,29 @@ public interface ConfigurationBuilder<T extends Configuration> extends Builder<T
 
     /**
      * Sets the interval at which the configuration file should be checked for changes.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
      * @param intervalSeconds The number of seconds that should pass between checks of the configuration file.
      * @return this builder instance.
      */
     ConfigurationBuilder<T> setMonitorInterval(String intervalSeconds);
 
+    /**
+     * Sets the interval at which the configuration file should be checked for changes.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
+     * @param intervalSeconds the number of seconds that should pass between checks of the configuraion file
+     * @return this builder instance.
+     */
+    ConfigurationBuilder<T> setMonitorInterval(int intervalSeconds);
+
     /**
      * Sets the list of packages to search for plugins.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
      * @param packages The comma separated list of packages.
      * @return this builder instance.
      */
@@ -427,30 +484,78 @@ public interface ConfigurationBuilder<T extends Configuration> extends Builder<T
 
     /**
      * Sets whether the shutdown hook should be disabled.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
      * @param flag "disable" will prevent the shutdown hook from being set.
      * @return this builder instance.
      */
     ConfigurationBuilder<T> setShutdownHook(String flag);
 
     /**
-     * How long appenders and background tasks will get to shutdown when the JVM shuts down.
-     * Default is zero which mean that each appender uses its default timeout, and don't wait for background
+     * How long appenders and background tasks will get to shut down when the JVM shuts down.
+     * The default is zero which mean that each appender uses its default timeout, and don't wait for background
      * tasks. Not all appenders will honor this, it is a hint and not an absolute guarantee that the shutdown
      * procedure will not take longer. Setting this too low increase the risk of losing outstanding log events
      * not yet written to the final destination. (Not used if {@link #setShutdownHook(String)} is set to "disable".)
+     * <p>
+     *     If the timeout value is set to {@code null} any previously configured value will be cleared.
+     * </p>
      * @return this builder instance.
-     *
+     * @throws NullPointerException if the given {@code timeUnit} is {@code null}
      * @see LoggerContext#stop(long, TimeUnit)
      */
     ConfigurationBuilder<T> setShutdownTimeout(long timeout, TimeUnit timeUnit);
 
+    /**
+     * Sets the shutdown timeout for appenders and background tasks (in the specified time-unit) to shut down
+     * when the JVM is shutdown
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
+     * @param timeout the timeout (in the given time-unit
+     * @param timeUnit the time-unit of the {@code timeout} value (i.e. SECONDS, MILLISECONDS)
+     * @return this builder instance
+     * @throws IllegalArgumentException if the {@code timeout} can not be converted to a valid {@code Long}
+     * @throws NullPointerException if the {@code timeUnit} is {@code null}
+     */
+    ConfigurationBuilder<T> setShutdownTimeout(String timeout, TimeUnit timeUnit);
+
+    /**
+     * Sets the shutdown timeout (in milliseconds) for appenders and background tasks to shut down when the JVM
+     * is shutdown.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
+     * @param timeoutMillis the timeout in milliseconds
+     * @return this builder instance
+     */
+    ConfigurationBuilder<T> setShutdownTimeout(long timeoutMillis);
+
     /**
      * Sets the level of the StatusLogger.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
      * @param level The logging level.
      * @return this builder instance.
      */
     ConfigurationBuilder<T> setStatusLevel(Level level);
 
+    /**
+     * Sets the level of the StatusLogger.
+     * <p>
+     *     If the given value is {@code null}, any previous value will be cleared.
+     * </p>
+     * <p>
+     *     If the given value is not {@code null}, it must be resolvable to a valid configured level
+     * </p>
+     * @param level The logging level
+     * @return this builder instance
+     * @throws IllegalArgumentException if the given argument is not a valid level
+     */
+    ConfigurationBuilder<T> setStatusLevel(String level);
+
     /**
      * Sets whether the logging should include constructing Plugins.
      * @param verbosity "disable" will hide messages from plugin construction.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
index 273906a5d48..6d91c2d89d8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java
@@ -20,7 +20,7 @@
  * @since 2.4
  */
 @Export
-@Version("2.20.1")
+@Version("2.25.0")
 package org.apache.logging.log4j.core.config.builder.api;
 
 import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
index f1ec35e06a0..07414399108 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java
@@ -20,103 +20,183 @@
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.AbstractConfiguration;
+import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationSource;
 import org.apache.logging.log4j.core.config.Node;
 import org.apache.logging.log4j.core.config.Reconfigurable;
 import org.apache.logging.log4j.core.config.builder.api.Component;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
 import org.apache.logging.log4j.core.config.plugins.util.PluginType;
 import org.apache.logging.log4j.core.config.status.StatusConfiguration;
+import org.apache.logging.log4j.core.net.Advertiser;
 import org.apache.logging.log4j.core.util.Patterns;
 
 /**
- * This is the general version of the Configuration created by the Builder. It may be extended to
- * enhance its functionality.
+ * A {@link Configuration} that is built (assembled) via a {@link ConfigurationBuilder}.
+ * <p>
+ *     This base implementation may be extended to enhance its functionality.
+ * </p>
+ * <h3>Important Notes for Extension:</h3>
+ * <p>
+ *     <ul>
+ *         <li>This class is designed to be extended for custom configurations. Subclasses should maintain
+ *         the contract of returning valid and non-{@code null} configuration components.</li>
+ *         <li>Fields such as {@code rootComponent} are intentionally made accessible to subclasses. However,
+ *         direct modification of such fields is discouraged to prevent inconsistencies.</li>
+ *         <li>Where possible, subclasses should adhere to the public accessor methods and override provided hooks
+ *         like {@link #setup()} to implement additional configuration logic.</li>
+ * </ul>
+ * </p>
+ * <h3>Backward Compatibility</h3>
+ * <p>
+ *     Certain design decisions in this class, such as the use of
+ *     a {@code protected} field for {@code rootComponent}, have been retained for historical reasons and due to
+ *     guarantees of binary compatibility for users of this class. Users of this API should avoid directly manipulating
+ *     internal fields where possible and instead rely on public or protected accessor methods where available.
+ * </p>
+ * <p>
+ *     This implementation is neither immutable nor thread-safe.
+ * </p>
  *
  * @since 2.4
  */
 public class BuiltConfiguration extends AbstractConfiguration {
+
     private final StatusConfiguration statusConfig;
-    protected Component rootComponent;
-    private Component loggersComponent;
-    private Component appendersComponent;
-    private Component filtersComponent;
-    private Component propertiesComponent;
-    private Component customLevelsComponent;
-    private Component scriptsComponent;
+
+    protected Component rootComponent; // should be private and accessed via getter, but backwards-compatibility...
+
     private String contentType = "text";
 
+    /**
+     * Constructs a new instance.
+     * <p>
+     *     The configuration assembled using the {@link ConfigurationBuilder} is transported via the given
+     *     {@code rootComponent} and used to populate this configuration's root {@code Node} in the
+     *     {@link #setup} method implementation.
+     * </p>
+     * <p>
+     *     This constructor is called via reflection from the {@link DefaultConfigurationBuilder}.
+     * </p>
+     *
+     * @param loggerContext the logger-context (can be {@code null})
+     * @param configurationSource the configuration-source
+     * @param rootComponent the root-component created by the builder
+     * @throws NullPointerException if the {@code source} or {@code rootComponent} argument is {@code null}
+     */
     public BuiltConfiguration(
-            final LoggerContext loggerContext, final ConfigurationSource source, final Component rootComponent) {
-        super(loggerContext, source);
-        statusConfig = new StatusConfiguration().withStatus(getDefaultStatus());
-        for (final Component component : rootComponent.getComponents()) {
-            switch (component.getPluginType()) {
-                case "Scripts": {
-                    scriptsComponent = component;
-                    break;
-                }
-                case "Loggers": {
-                    loggersComponent = component;
-                    break;
-                }
-                case "Appenders": {
-                    appendersComponent = component;
-                    break;
-                }
-                case "Filters": {
-                    filtersComponent = component;
-                    break;
-                }
-                case "Properties": {
-                    propertiesComponent = component;
-                    break;
-                }
-                case "CustomLevels": {
-                    customLevelsComponent = component;
-                    break;
-                }
+            final LoggerContext loggerContext,
+            final ConfigurationSource configurationSource,
+            final Component rootComponent) {
+
+        super(
+                loggerContext,
+                Objects.requireNonNull(configurationSource, "The 'configurationSource' argument cannot be null"));
+
+        Objects.requireNonNull(rootComponent, "The 'rootComponent' argument cannot be null");
+
+        this.rootComponent = rootComponent;
+        this.statusConfig = new StatusConfiguration().withStatus(getDefaultStatus());
+
+        // process each of the root-component's attributes, dealing with special cases and assigning all to root node
+        for (Map.Entry<String, String> entry : rootComponent.getAttributes().entrySet()) {
+            final String key = entry.getKey().trim();
+            final String value = entry.getValue();
+            this.getRootNode().getAttributes().put(key, value); // all attributes get passed to root-node
+            if ("advertise".equalsIgnoreCase(key)) {
+                createAdvertiser(value, getConfigurationSource());
+            } else if ("dest".equalsIgnoreCase(key)) {
+                statusConfig.withDestination(value);
+            } else if ("monitorInterval".equalsIgnoreCase(key)) {
+                setMonitorInterval(value, TimeUnit.SECONDS);
+            } else if ("name".equalsIgnoreCase(key)) {
+                setName(value);
+            } else if ("packages".equalsIgnoreCase(key)) {
+                setPluginPackages(value);
+            } else if ("shutdownHook".equalsIgnoreCase(key)) {
+                setShutdownHook(value);
+            } else if ("shutdownTimeout".equalsIgnoreCase(key)) {
+                setShutdownTimeout(value, TimeUnit.MILLISECONDS);
+            } else if ("status".equalsIgnoreCase(key)) {
+                statusConfig.withStatus(value);
             }
         }
-        this.rootComponent = rootComponent;
+
+        this.statusConfig.initialize();
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     *     Converts the {@code Component} children of the root component to Log4j configuration nodes and
+     *     subsequently invalidates the root component.
+     * </p>
+     */
     @Override
     public void setup() {
+
         final List<Node> children = rootNode.getChildren();
-        if (propertiesComponent.getComponents().size() > 0) {
-            children.add(convertToNode(rootNode, propertiesComponent));
-        }
-        if (scriptsComponent.getComponents().size() > 0) {
-            children.add(convertToNode(rootNode, scriptsComponent));
-        }
-        if (customLevelsComponent.getComponents().size() > 0) {
-            children.add(convertToNode(rootNode, customLevelsComponent));
-        }
-        children.add(convertToNode(rootNode, loggersComponent));
-        children.add(convertToNode(rootNode, appendersComponent));
-        if (filtersComponent.getComponents().size() > 0) {
-            if (filtersComponent.getComponents().size() == 1) {
-                children.add(
-                        convertToNode(rootNode, filtersComponent.getComponents().get(0)));
-            } else {
-                children.add(convertToNode(rootNode, filtersComponent));
-            }
+
+        if (this.rootComponent != null) {
+
+            getChildComponent("Properties")
+                    .filter(c -> !c.getComponents().isEmpty())
+                    .ifPresent(c -> children.add(convertToNode(rootNode, c)));
+            getChildComponent("Scripts")
+                    .filter(c -> !c.getComponents().isEmpty())
+                    .ifPresent(c -> children.add(convertToNode(rootNode, c)));
+            getChildComponent("CustomLevels")
+                    .filter(c -> !c.getComponents().isEmpty())
+                    .ifPresent(c -> children.add(convertToNode(rootNode, c)));
+
+            children.add(convertToNode(rootNode, getChildComponent("Loggers").orElse(new Component("Loggers"))));
+            children.add(convertToNode(rootNode, getChildComponent("Appenders").orElse(new Component("Appenders"))));
+
+            getChildComponent("Filters")
+                    .filter(c -> !c.getComponents().isEmpty())
+                    .ifPresent(c -> {
+                        if (c.getComponents().size() == 1) {
+                            children.add(
+                                    convertToNode(rootNode, c.getComponents().get(0)));
+                        } else {
+                            children.add(convertToNode(rootNode, c));
+                        }
+                    });
         }
-        rootComponent = null;
+
+        this.rootComponent = null;
     }
 
+    /**
+     * Gets the content-type of the built configuration.
+     * @return the content-type
+     */
     public String getContentType() {
         return this.contentType;
     }
 
+    /**
+     * Sets the content-type of the build configuration.
+     * @param contentType the content-type
+     */
     public void setContentType(final String contentType) {
         this.contentType = contentType;
     }
 
-    public void createAdvertiser(final String advertiserString, final ConfigurationSource configSource) {
+    /**
+     * Creates and sets this configuration's {@link Advertiser} for the given configuration-source.
+     * @param name the name of the advertiser
+     * @param configSource the configuration-source
+     */
+    public void createAdvertiser(final String name, final ConfigurationSource configSource) {
         byte[] buffer = null;
         try {
             if (configSource != null) {
@@ -126,39 +206,146 @@ public void createAdvertiser(final String advertiserString, final ConfigurationS
                 }
             }
         } catch (final IOException ioe) {
-            LOGGER.warn("Unable to read configuration source " + configSource.toString());
+            LOGGER.warn("Unable to read configuration source {}", configSource);
         }
-        super.createAdvertiser(advertiserString, configSource, buffer, contentType);
+        super.createAdvertiser(name, configSource, buffer, contentType);
     }
 
+    /**
+     * Returns the status-logger fallback listener configuration.
+     * <p>
+     *     Implementations should always return a non-{@code null} value.
+     * </p>
+     * @return the status configuration
+     */
     public StatusConfiguration getStatusConfiguration() {
         return statusConfig;
     }
 
+    /**
+     * Sets the packages to search for plugin implementations.
+     * @param packages a comma-separated list of package names
+     */
     public void setPluginPackages(final String packages) {
-        pluginPackages.addAll(Arrays.asList(packages.split(Patterns.COMMA_SEPARATOR)));
+        Objects.requireNonNull(packages, "The 'packages' argument cannot be null");
+        List<String> packageList = Arrays.stream(packages.split(Patterns.COMMA_SEPARATOR))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toList());
+        pluginPackages.addAll(packageList);
     }
 
+    /**
+     * Sets the shutdown hook flag.
+     * @param flag if "disable" the shutdown hook will be disabled; otherwise, all other values enable it
+     */
     public void setShutdownHook(final String flag) {
-        isShutdownHookEnabled = !"disable".equalsIgnoreCase(flag);
+        setShutdownHook(!"disable".equalsIgnoreCase(flag));
     }
 
-    public void setShutdownTimeoutMillis(final long shutdownTimeoutMillis) {
-        this.shutdownTimeoutMillis = shutdownTimeoutMillis;
+    /**
+     * Sets the enablement of the shutdown hook.
+     * @param flag {@code true} to enable the shutdown hook (default); otherwise, {@code false} to disable
+     */
+    public void setShutdownHook(final boolean flag) {
+        isShutdownHookEnabled = flag;
     }
 
-    public void setMonitorInterval(final int intervalSeconds) {
-        if (this instanceof Reconfigurable && intervalSeconds > 0) {
-            initializeWatchers((Reconfigurable) this, getConfigurationSource(), intervalSeconds);
+    /**
+     * Sets the shutdown timeout to the given value converted to milliseconds using the provided time-unit.
+     * @param value the value
+     * @param timeUnit the time-unit of the value (i.e. MILLISECONDS, SECONDS, MINUTES, etc.)
+     */
+    public void setShutdownTimeout(final String value, final TimeUnit timeUnit) {
+        Objects.requireNonNull(value, "The 'value' argument cannot be null");
+        Objects.requireNonNull(timeUnit, "The 'timeUnit' argument cannot be null");
+        try {
+            setShutdownTimeoutMillis(timeUnit.toMillis(Long.parseLong(value)));
+        } catch (final Exception ex) {
+            LOGGER.error("The given shudown timeoutt is invalid '{}'. Reason: {}", value, ex.getMessage());
         }
     }
 
-    @Override
-    public PluginManager getPluginManager() {
-        return pluginManager;
+    /**
+     * Sets the shutdown timeout in milliseconds.
+     * @param millis the number of milliseconds to set
+     */
+    public void setShutdownTimeoutMillis(final long millis) {
+        this.shutdownTimeoutMillis = millis;
+    }
+
+    /**
+     * Sets the monitor interval to the given value converted to seconds using the provided time-unit.
+     * @param value the value
+     * @param timeUnit the time-unit of the value (i.e. MILLISECONDS, SECONDS, MINUTES, etc.)
+     */
+    public void setMonitorInterval(final String value, final TimeUnit timeUnit) {
+        Objects.requireNonNull(value, "The 'value' argument cannot be null");
+        Objects.requireNonNull(timeUnit, "The 'timeUnit' argument cannot be null");
+        try {
+            setMonitorInterval(Math.toIntExact(timeUnit.toSeconds(Integer.parseInt(value))));
+        } catch (final Exception ex) {
+            LOGGER.error("The given monitor interval is invalid '{}'. Reason: {}", value, ex.getMessage());
+        }
+    }
+
+    /**
+     * Sets the monitor interval to the given number of seconds.
+     * <p>
+     *     If the given value is greater than 0 and this instance implements {@link Reconfigurable},
+     *     this method will trigger the {@link #initializeWatchers(Reconfigurable, ConfigurationSource, int)}
+     *     method.
+     * </p>
+     * @param seconds the number of seconds to set
+     */
+    public void setMonitorInterval(final int seconds) {
+        if (this instanceof Reconfigurable && seconds > 0) {
+            initializeWatchers((Reconfigurable) this, getConfigurationSource(), seconds);
+        }
+    }
+
+    /**
+     * Gets the root component.
+     * <p>
+     *     NOTE: After {@link #setup()} has been called this will always return an empty optional.
+     * </p>
+     * @return an optional containing the root component or that is <i>empty</i> if undefined
+     */
+    protected final Optional<Component> getRootComponent() {
+        return Optional.ofNullable(rootComponent);
+    }
+
+    /**
+     * Gets the child component of the root component with the given plugin-type.
+     * @param pluginType the plugin-type to lookup
+     * @return an optional containing the resolved child component or that is <i>empty</i> if not found
+     */
+    protected final Optional<Component> getChildComponent(final String pluginType) {
+        Objects.requireNonNull(pluginType, "The 'pluginType' argument cannot be null");
+        return Optional.ofNullable(getChildComponents().get(pluginType));
+    }
+
+    /**
+     * Returns a map of all "named" child components.
+     * @return an <i>immutable</i> map of child components keyed by name
+     */
+    protected final Map<String, Component> getChildComponents() {
+
+        return this.rootComponent.getComponents().stream()
+                .filter(c -> Objects.nonNull(c.getPluginType()))
+                .collect(Collectors.toMap(Component::getPluginType, Function.identity()));
     }
 
+    /**
+     * Converts the given configuration {@link Component} to a runtime configuration {@link Node}
+     * with the given parent node.
+     * @param parent the target parent node
+     * @param component the component to convert
+     * @return the constructed node
+     */
     protected Node convertToNode(final Node parent, final Component component) {
+        Objects.requireNonNull(parent, "The 'parent' argument cannot be null");
+        Objects.requireNonNull(component, "The 'component' argument cannot be null");
         final String name = component.getPluginType();
         final PluginType<?> pluginType = pluginManager.getPluginType(name);
         final Node node = new Node(parent, name, pluginType);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
index 633e619b281..593a0efb5b9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
@@ -22,8 +22,9 @@
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.lang.reflect.Constructor;
-import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
@@ -32,7 +33,6 @@
 import javax.xml.transform.Result;
 import javax.xml.transform.Source;
 import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.TransformerFactoryConfigurationError;
@@ -41,7 +41,6 @@
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.Configuration;
 import org.apache.logging.log4j.core.config.ConfigurationException;
 import org.apache.logging.log4j.core.config.ConfigurationSource;
 import org.apache.logging.log4j.core.config.LoggerConfig;
@@ -63,555 +62,930 @@
 import org.apache.logging.log4j.core.util.Throwables;
 
 /**
+ * A default ConfigurationBuilder that is used to build a BuiltConfiguration instance.
+ *
  * @param <T> The BuiltConfiguration type.
  * @since 2.4
  */
 public class DefaultConfigurationBuilder<T extends BuiltConfiguration> implements ConfigurationBuilder<T> {
 
-    private static final String INDENT = "  ";
+    /** Indentation prefix used when generating an XML representation of this builder's configuration. */
+    private static final String XML_INDENT = "  ";
+
+    /** The class of the configuration instance being built. */
+    private final Class<T> configurationClass;
 
+    /** The root component. */
     private final Component root = new Component();
-    private Component loggers;
-    private Component appenders;
-    private Component filters;
-    private Component properties;
-    private Component customLevels;
-    private Component scripts;
-    private final Class<T> clazz;
+
+    /** Standard component: "Appenders". */
+    private final Component appenders;
+
+    /** Standard component: "CustomLevels". */
+    private final Component customLevels;
+
+    /** Standard component: "Filters". */
+    private final Component filters;
+
+    /** Standard component: "Loggers". */
+    private final Component loggers;
+
+    /** Standard component: "Properties". */
+    private final Component properties;
+
+    /** Standard component: "Scripts". */
+    private final Component scripts;
+
+    /** The configuration source passed to the constructor of the built configuration instance. */
     private ConfigurationSource source;
-    private int monitorInterval;
-    private Level level;
-    private String destination;
-    private String packages;
-    private String shutdownFlag;
-    private long shutdownTimeoutMillis;
-    private String advertiser;
-    private LoggerContext loggerContext;
-    private String name;
 
-    @SuppressFBWarnings(
-            value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"},
-            justification = "This method only uses internally generated data.")
-    public static void formatXml(final Source source, final Result result)
-            throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
-        final Transformer transformer = TransformerFactory.newInstance().newTransformer();
-        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(INDENT.length()));
-        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-        transformer.transform(source, result);
-    }
+    /** The logger context passed to the constructor of the built configuration instance. */
+    private LoggerContext loggerContext;
 
+    /**
+     * Constructs a new instance with a standard {@link BuiltConfiguration} target type.
+     */
     @SuppressWarnings("unchecked")
     public DefaultConfigurationBuilder() {
         this((Class<T>) BuiltConfiguration.class);
         root.addAttribute("name", "Built");
     }
 
+    /**
+     * Constructs a new instance with the given {@link BuiltConfiguration} implementation class.
+     * @param clazz the {@code Class} of the {@code BuiltConfiguration} implementation to build
+     * @throws NullPointerException if the argument is null
+     */
     public DefaultConfigurationBuilder(final Class<T> clazz) {
-        if (clazz == null) {
-            throw new IllegalArgumentException("A Configuration class must be provided");
-        }
-        this.clazz = clazz;
-        final List<Component> components = root.getComponents();
-        properties = new Component("Properties");
-        components.add(properties);
-        scripts = new Component("Scripts");
-        components.add(scripts);
-        customLevels = new Component("CustomLevels");
-        components.add(customLevels);
-        filters = new Component("Filters");
-        components.add(filters);
-        appenders = new Component("Appenders");
-        components.add(appenders);
-        loggers = new Component("Loggers");
-        components.add(loggers);
+
+        super();
+
+        this.configurationClass = Objects.requireNonNull(clazz, "The 'configurationClass' argument must not be null.");
+        this.root.addAttribute("name", "Built");
+
+        properties = getOrCreateComponent("Properties");
+        scripts = getOrCreateComponent("Scripts");
+        customLevels = getOrCreateComponent("CustomLevels");
+        filters = getOrCreateComponent("Filters");
+        appenders = getOrCreateComponent("Appenders");
+        loggers = getOrCreateComponent("Loggers");
     }
 
+    //
+    // ACCESSORS / MUTATORS
+    //
+
+    /**
+     * Adds the component built by the provided builder as a child component of the given parent component.
+     *
+     * @param parent the parent component to add to
+     * @param builder the builder to generate the child component from
+     * @throws ConfigurationException if an error occurs while building the given builder
+     * @throws NullPointerException if either argument is null
+     */
     protected ConfigurationBuilder<T> add(final Component parent, final ComponentBuilder<?> builder) {
+        Objects.requireNonNull(parent, "The 'parent' component must not be null.");
+        Objects.requireNonNull(builder, "The 'builder' component must not be null.");
         parent.getComponents().add(builder.build());
         return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> add(final AppenderComponentBuilder builder) {
-        return add(appenders, builder);
+        add(this.appenders, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> add(final CustomLevelComponentBuilder builder) {
-        return add(customLevels, builder);
+        add(this.customLevels, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> add(final FilterComponentBuilder builder) {
-        return add(filters, builder);
+        add(this.filters, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> add(final ScriptComponentBuilder builder) {
-        return add(scripts, builder);
+        add(this.scripts, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> add(final ScriptFileComponentBuilder builder) {
-        return add(scripts, builder);
+        add(this.scripts, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> add(final LoggerComponentBuilder builder) {
-        return add(loggers, builder);
+        add(this.loggers, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
-    public ConfigurationBuilder<T> add(final RootLoggerComponentBuilder builder) {
+    public ConfigurationBuilder<T> add(RootLoggerComponentBuilder builder) {
         for (final Component c : loggers.getComponents()) {
             if (c.getPluginType().equals(LoggerConfig.ROOT)) {
                 throw new ConfigurationException("Root Logger was previously defined");
             }
         }
-        return add(loggers, builder);
+        add(this.loggers, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
     public ConfigurationBuilder<T> addProperty(final String key, final String value) {
-        properties.addComponent(newComponent(key, "Property", value).build());
+        add(this.properties, newProperty(key, value));
         return this;
     }
 
+    /** {@inheritDoc} */
     @Override
-    public T build() {
-        return build(true);
+    public ConfigurationBuilder<T> addProperty(final PropertyComponentBuilder builder) {
+        add(this.properties, builder);
+        return this;
     }
 
+    /** {@inheritDoc} */
     @Override
-    public T build(final boolean initialize) {
-        T configuration;
+    public ConfigurationBuilder<T> addRootProperty(String key, String value) {
+        Objects.requireNonNull(key, "The 'key' argument must not be null.");
+        if (key.isEmpty()) {
+            throw new IllegalArgumentException("The 'key' argument must not be empty.");
+        }
+        if (value != null) {
+            this.getRootComponent().getAttributes().put(key, value);
+        } else {
+            this.getRootComponent().getAttributes().remove(key);
+        }
+        return this;
+    }
+
+    /**
+     * Gets the value of a property (attribute) on the root component.
+     * @param key the property key
+     * @return an optional containing the property value or that is <i>empty</i> if not set
+     */
+    protected Optional<String> getRootProperty(String key) {
+        Objects.requireNonNull(key, "The 'key' argument must not be null.");
+        if (key.isEmpty()) {
+            throw new IllegalArgumentException("The 'key' argument must not be empty.");
+        }
+        return Optional.ofNullable(this.getRootComponent().getAttributes().get(key));
+    }
+
+    /**
+     * Gets the child component with the given plugin-type.
+     * @param pluginType the plugin-type to lookup
+     * @return an optional containing the resolved component or that is <i>empty</i> if not found
+     */
+    protected Optional<Component> getComponent(final String pluginType) {
+        Objects.requireNonNull(pluginType, "The 'pluginType' argument must not be null.");
+        return this.root.getComponents().stream()
+                .filter(c -> c.getPluginType().equals(pluginType))
+                .findFirst();
+    }
+
+    /**
+     * Gets the root component.
+     *
+     * @return the root component (will never be null)
+     */
+    protected Component getRootComponent() {
+        return this.root;
+    }
+
+    /**
+     * Gets or creates the child component with the given plugin-type.
+     * <p>
+     *     The lookup is case-sensitive.
+     * </p>
+     *
+     * @param pluginType the plugin-type to lookup
+     * @return either the existing component or a new component with the given plugin-type if not found (never null)
+     */
+    protected Component getOrCreateComponent(String pluginType) {
+        Objects.requireNonNull(pluginType, "The 'pluginType' must not be null.");
+        return getComponent(pluginType).orElseGet(() -> {
+            Component c = new Component(pluginType);
+            this.root.getComponents().add(c);
+            return c;
+        });
+    }
+
+    /**
+     * Gets the value of the "advertiser" property.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<String> getAdvertiser() {
+        return getRootProperty("advertiser");
+    }
+
+    /**
+     * Gets the value of the "name" property.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<String> getConfigurationName() {
+        return getRootProperty("name");
+    }
+
+    /**
+     * Gets the configuration-source.
+     * @return an optional containing the configuration-source or that is <i>empy</i> if undefined
+     */
+    protected Optional<ConfigurationSource> getConfigurationSource() {
+        return Optional.ofNullable(this.source);
+    }
+
+    /**
+     * Gets the value of the "dest" property.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<String> getDestination() {
+        return getRootProperty("dest");
+    }
+
+    /**
+     * Gets the logger-context.
+     *
+     * @return an optional containing the logger-context or that is <i>empty</i> if undefined
+     */
+    protected Optional<LoggerContext> getLoggerContext() {
+        return Optional.ofNullable(this.loggerContext);
+    }
+
+    /**
+     * Returns the configured monitor interval (in seconds) as an {@code Integer}.
+     * @return an optional containing the configured monitor interval or that is <i>empty</i> if undefined (or invalid)
+     */
+    protected Optional<Integer> getMonitorInterval() {
         try {
-            if (source == null) {
-                source = ConfigurationSource.NULL_SOURCE;
-            }
-            final Constructor<T> constructor =
-                    clazz.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class);
-            configuration = constructor.newInstance(loggerContext, source, root);
-            configuration.getRootNode().getAttributes().putAll(root.getAttributes());
-            if (name != null) {
-                configuration.setName(name);
-            }
-            if (level != null) {
-                configuration.getStatusConfiguration().withStatus(level);
-            }
-            if (destination != null) {
-                configuration.getStatusConfiguration().withDestination(destination);
-            }
-            if (packages != null) {
-                configuration.setPluginPackages(packages);
-            }
-            if (shutdownFlag != null) {
-                configuration.setShutdownHook(shutdownFlag);
-            }
-            if (shutdownTimeoutMillis > 0) {
-                configuration.setShutdownTimeoutMillis(shutdownTimeoutMillis);
-            }
-            if (advertiser != null) {
-                configuration.createAdvertiser(advertiser, source);
-            }
-            configuration.setMonitorInterval(monitorInterval);
+            return getRootProperty("monitorInterval").map(Integer::parseInt);
         } catch (final Exception ex) {
-            throw new IllegalArgumentException("Invalid Configuration class specified", ex);
+            return Optional.empty();
         }
-        configuration.getStatusConfiguration().initialize();
-        if (initialize) {
-            configuration.initialize();
+    }
+
+    /**
+     * Gets the value of the "packages" property.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<String> getPackages() {
+        return getRootProperty("packages");
+    }
+
+    /**
+     * Gets the value of the "shutdownHook" property.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<String> getShutdownHook() {
+        return getRootProperty("shutdownHook");
+    }
+
+    /**
+     * Gets the value of the "shutdownTimeout" property as a {@code Long}.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<Long> getShutdownTimeout() {
+        try {
+            return getRootProperty("shutdownTimeout").map(Long::parseLong);
+        } catch (final Exception ex) {
+            return Optional.empty();
         }
-        return configuration;
     }
 
-    private String formatXml(final String xml)
-            throws TransformerConfigurationException, TransformerException, TransformerFactoryConfigurationError {
-        final StringWriter writer = new StringWriter();
-        formatXml(new StreamSource(new StringReader(xml)), new StreamResult(writer));
-        return writer.toString();
+    /**
+     * Gets the value of the "status" property as a {@code Level}.
+     * @return an optional containing the property value or that is <i>empty</i> if undefined
+     */
+    protected Optional<Level> getStatusLevel() {
+        return getRootProperty("status").map(Level::getLevel);
     }
 
+    /**
+     * Gets the "Appenders" component.
+     *
+     * @return the appenders component
+     */
+    protected final Component getAppenders() {
+        return this.appenders;
+    }
+
+    /**
+     * Gets the "CustomLevels" component.
+     *
+     * @return the custom-levels component
+     */
+    protected final Component getCustomLevels() {
+        return this.customLevels;
+    }
+
+    /**
+     * Gets the "Filters" component.
+     *
+     * @return the filters component
+     */
+    protected final Component getFilters() {
+        return this.filters;
+    }
+
+    /**
+     * Gets the "Loggers" component.
+     *
+     * @return the loggers component
+     */
+    protected final Component getLoggers() {
+        return this.loggers;
+    }
+
+    /**
+     * Gets the "Properties" component.
+     *
+     * @return the properties component
+     */
+    protected final Component getProperties() {
+        return this.properties;
+    }
+
+    /**
+     * Gets the "Scripts" component.
+     *
+     * @return the scripts component
+     */
+    protected final Component getScripts() {
+        return this.scripts;
+    }
+
+    /** {@inheritDoc} */
     @Override
-    public void writeXmlConfiguration(final OutputStream output) throws IOException {
-        try {
-            final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
-            writeXmlConfiguration(xmlWriter);
-            xmlWriter.close();
-        } catch (final XMLStreamException e) {
-            if (e.getNestedException() instanceof IOException) {
-                throw (IOException) e.getNestedException();
+    public ConfigurationBuilder<T> setAdvertiser(final String advertiser) {
+        this.addRootProperty("advertiser", advertiser);
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setConfigurationName(final String name) {
+        this.addRootProperty("name", name);
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setConfigurationSource(final ConfigurationSource configurationSource) {
+        source = configurationSource;
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setDestination(final String destination) {
+        this.addRootProperty("dest", (destination == null) ? null : destination.trim());
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setMonitorInterval(final String intervalSeconds) {
+        int iMonitorInterval = 0;
+        if (intervalSeconds != null && !intervalSeconds.isEmpty() && !"0".equals(intervalSeconds.trim())) {
+            try {
+                iMonitorInterval = Integers.parseInt(intervalSeconds.trim());
+            } catch (final Exception ex) {
+                throw new IllegalArgumentException("Invalid monitor interval: " + intervalSeconds, ex);
             }
-            Throwables.rethrow(e);
         }
+        return this.setMonitorInterval(iMonitorInterval);
     }
 
+    /** {@inheritDoc} */
     @Override
-    public String toXmlConfiguration() {
-        final StringWriter writer = new StringWriter();
-        try {
-            final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
-            writeXmlConfiguration(xmlWriter);
-            xmlWriter.close();
-            return formatXml(writer.toString());
-        } catch (final XMLStreamException | TransformerException e) {
-            Throwables.rethrow(e);
-        }
-        return writer.toString();
+    public ConfigurationBuilder<T> setMonitorInterval(final int intervalSeconds) {
+        addRootProperty("monitorInterval", intervalSeconds <= 0 ? null : String.valueOf(intervalSeconds));
+        return this;
     }
 
-    private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException {
-        xmlWriter.writeStartDocument();
-        xmlWriter.writeStartElement("Configuration");
-        if (name != null) {
-            xmlWriter.writeAttribute("name", name);
-        }
-        if (level != null) {
-            xmlWriter.writeAttribute("status", level.name());
-        }
-        if (destination != null) {
-            xmlWriter.writeAttribute("dest", destination);
-        }
-        if (packages != null) {
-            xmlWriter.writeAttribute("packages", packages);
-        }
-        if (shutdownFlag != null) {
-            xmlWriter.writeAttribute("shutdownHook", shutdownFlag);
-        }
-        if (shutdownTimeoutMillis > 0) {
-            xmlWriter.writeAttribute("shutdownTimeout", String.valueOf(shutdownTimeoutMillis));
-        }
-        if (advertiser != null) {
-            xmlWriter.writeAttribute("advertiser", advertiser);
-        }
-        if (monitorInterval > 0) {
-            xmlWriter.writeAttribute("monitorInterval", String.valueOf(monitorInterval));
-        }
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setPackages(final String packages) {
+        addRootProperty("packages", packages);
+        return this;
+    }
 
-        writeXmlSection(xmlWriter, properties);
-        writeXmlSection(xmlWriter, scripts);
-        writeXmlSection(xmlWriter, customLevels);
-        if (filters.getComponents().size() == 1) {
-            writeXmlComponent(xmlWriter, filters.getComponents().get(0));
-        } else if (filters.getComponents().size() > 1) {
-            writeXmlSection(xmlWriter, filters);
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setShutdownHook(final String flag) {
+        addRootProperty("shutdownHook", flag);
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setShutdownTimeout(final String timeout, final TimeUnit timeUnit) {
+        Objects.requireNonNull(timeUnit, "The 'timeUnit' argument must not be null.");
+        long shutdownTimeoutMillis = 0L;
+        if (timeout != null && !timeout.isEmpty()) {
+            try {
+                shutdownTimeoutMillis = timeUnit.toMillis(Long.parseLong(timeout));
+            } catch (final Exception ex) {
+                throw new IllegalArgumentException("Invalid shutdown timeout: " + timeout, ex);
+            }
         }
-        writeXmlSection(xmlWriter, appenders);
-        writeXmlSection(xmlWriter, loggers);
+        return this.setShutdownTimeout(shutdownTimeoutMillis, timeUnit);
+    }
 
-        xmlWriter.writeEndElement(); // "Configuration"
-        xmlWriter.writeEndDocument();
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setShutdownTimeout(final long timeout, final TimeUnit timeUnit) {
+        Objects.requireNonNull(timeUnit, "The 'timeUnit' argument must not be null.");
+        return this.setShutdownTimeout(timeUnit.toMillis(timeout));
     }
 
-    private void writeXmlSection(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
-        if (!component.getAttributes().isEmpty()
-                || !component.getComponents().isEmpty()
-                || component.getValue() != null) {
-            writeXmlComponent(xmlWriter, component);
-        }
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setShutdownTimeout(final long timeoutMillis) {
+        this.addRootProperty("shutdownTimeout", timeoutMillis <= 0L ? null : String.valueOf(timeoutMillis));
+        return this;
     }
 
-    private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component)
-            throws XMLStreamException {
-        if (!component.getComponents().isEmpty() || component.getValue() != null) {
-            xmlWriter.writeStartElement(component.getPluginType());
-            writeXmlAttributes(xmlWriter, component);
-            for (final Component subComponent : component.getComponents()) {
-                writeXmlComponent(xmlWriter, subComponent);
-            }
-            if (component.getValue() != null) {
-                xmlWriter.writeCharacters(component.getValue());
-            }
-            xmlWriter.writeEndElement();
-        } else {
-            xmlWriter.writeEmptyElement(component.getPluginType());
-            writeXmlAttributes(xmlWriter, component);
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setStatusLevel(final Level level) {
+        this.addRootProperty("status", (level != null) ? level.toString() : null);
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConfigurationBuilder<T> setStatusLevel(final String level) {
+        return this.setStatusLevel((level != null) ? Level.getLevel(level.trim()) : null);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @deprecated This method is ineffective and only kept for binary backward compatibility.
+     */
+    @Override
+    @Deprecated
+    public ConfigurationBuilder<T> setVerbosity(final String verbosity) {
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setLoggerContext(final LoggerContext loggerContext) {
+        this.loggerContext = loggerContext;
+    }
+
+    //
+    // BUILD
+    //
+
+    /** {@inheritDoc} */
+    @Override
+    public T build() {
+        return build(true);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T build(final boolean initialize) {
+        if (source == null) {
+            source = ConfigurationSource.NULL_SOURCE;
         }
+        T configuration = createNewConfigurationInstance(this.configurationClass);
+        if (initialize) {
+            configuration.initialize();
+        }
+        return configuration;
     }
 
-    private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component)
-            throws XMLStreamException {
-        for (final Map.Entry<String, String> attribute :
-                component.getAttributes().entrySet()) {
-            xmlWriter.writeAttribute(attribute.getKey(), attribute.getValue());
+    /**
+     * Instantiate a new instance of the {@code Configuration} implementation.
+     * <p>
+     *     Subclasses may override this if they need to provide some non-standard behaviour.
+     * </p>
+     *
+     * @return the new configuration instance
+     * @throws IllegalStateException if the configuration cannot be instantiated
+     */
+    protected T createNewConfigurationInstance(Class<T> configurationClass) {
+        Objects.requireNonNull(configurationClass, "The 'configurationClass' argument must not be null.");
+        try {
+            final Constructor<T> constructor =
+                    configurationClass.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class);
+            return constructor.newInstance(loggerContext, source, root);
+        } catch (final Exception ex) {
+            throw new IllegalStateException(
+                    "Configuration class '" + configurationClass.getName() + "' cannot be instantiated.", ex);
         }
     }
 
+    //
+    // BUILDER FACTORY METHODS
+    //
+
+    /** {@inheritDoc} */
     @Override
     public ScriptComponentBuilder newScript(final String name, final String language, final String text) {
         return new DefaultScriptComponentBuilder(this, name, language, text);
     }
 
+    /** {@inheritDoc} */
     @Override
     public ScriptFileComponentBuilder newScriptFile(final String path) {
         return new DefaultScriptFileComponentBuilder(this, path, path);
     }
 
+    /** {@inheritDoc} */
     @Override
     public ScriptFileComponentBuilder newScriptFile(final String name, final String path) {
         return new DefaultScriptFileComponentBuilder(this, name, path);
     }
 
+    /** {@inheritDoc} */
     @Override
     public AppenderComponentBuilder newAppender(final String name, final String type) {
         return new DefaultAppenderComponentBuilder(this, name, type);
     }
 
+    /** {@inheritDoc} */
     @Override
     public AppenderRefComponentBuilder newAppenderRef(final String ref) {
         return new DefaultAppenderRefComponentBuilder(this, ref);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newAsyncLogger(final String name) {
         return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger");
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newAsyncLogger(final String name, final boolean includeLocation) {
         return new DefaultLoggerComponentBuilder(this, name, null, "AsyncLogger", includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newAsyncLogger(final String name, final Level level) {
         return new DefaultLoggerComponentBuilder(this, name, level.toString(), "AsyncLogger");
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newAsyncLogger(final String name, final Level level, final boolean includeLocation) {
         return new DefaultLoggerComponentBuilder(this, name, level.toString(), "AsyncLogger", includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newAsyncLogger(final String name, final String level) {
         return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger");
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newAsyncLogger(final String name, final String level, final boolean includeLocation) {
         return new DefaultLoggerComponentBuilder(this, name, level, "AsyncLogger", includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newAsyncRootLogger() {
         return new DefaultRootLoggerComponentBuilder(this, "AsyncRoot");
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newAsyncRootLogger(final boolean includeLocation) {
         return new DefaultRootLoggerComponentBuilder(this, null, "AsyncRoot", includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newAsyncRootLogger(final Level level) {
         return new DefaultRootLoggerComponentBuilder(this, level.toString(), "AsyncRoot");
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newAsyncRootLogger(final Level level, final boolean includeLocation) {
         return new DefaultRootLoggerComponentBuilder(this, level.toString(), "AsyncRoot", includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newAsyncRootLogger(final String level) {
         return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot");
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newAsyncRootLogger(final String level, final boolean includeLocation) {
         return new DefaultRootLoggerComponentBuilder(this, level, "AsyncRoot", includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(final String type) {
         return new DefaultComponentBuilder<>(this, type);
     }
 
+    /** {@inheritDoc} */
     @Override
     public <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(final String name, final String type) {
         return new DefaultComponentBuilder<>(this, name, type);
     }
 
+    /** {@inheritDoc} */
     @Override
     public <B extends ComponentBuilder<B>> ComponentBuilder<B> newComponent(
             final String name, final String type, final String value) {
         return new DefaultComponentBuilder<>(this, name, type, value);
     }
 
+    /** {@inheritDoc} */
     @Override
     public PropertyComponentBuilder newProperty(final String name, final String value) {
         return new DefaultPropertyComponentBuilder(this, name, value);
     }
 
+    /** {@inheritDoc} */
     @Override
     public KeyValuePairComponentBuilder newKeyValuePair(final String key, final String value) {
         return new DefaultKeyValuePairComponentBuilder(this, key, value);
     }
 
+    /** {@inheritDoc} */
     @Override
     public CustomLevelComponentBuilder newCustomLevel(final String name, final int level) {
         return new DefaultCustomLevelComponentBuilder(this, name, level);
     }
 
+    /** {@inheritDoc} */
     @Override
     public FilterComponentBuilder newFilter(
             final String type, final Filter.Result onMatch, final Filter.Result onMismatch) {
         return new DefaultFilterComponentBuilder(this, type, onMatch.name(), onMismatch.name());
     }
 
+    /** {@inheritDoc} */
     @Override
     public FilterComponentBuilder newFilter(final String type, final String onMatch, final String onMismatch) {
         return new DefaultFilterComponentBuilder(this, type, onMatch, onMismatch);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LayoutComponentBuilder newLayout(final String type) {
         return new DefaultLayoutComponentBuilder(this, type);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newLogger(final String name) {
         return new DefaultLoggerComponentBuilder(this, name, null);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newLogger(final String name, final boolean includeLocation) {
         return new DefaultLoggerComponentBuilder(this, name, null, includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newLogger(final String name, final Level level) {
         return new DefaultLoggerComponentBuilder(this, name, level.toString());
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newLogger(final String name, final Level level, final boolean includeLocation) {
         return new DefaultLoggerComponentBuilder(this, name, level.toString(), includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newLogger(final String name, final String level) {
         return new DefaultLoggerComponentBuilder(this, name, level);
     }
 
+    /** {@inheritDoc} */
     @Override
     public LoggerComponentBuilder newLogger(final String name, final String level, final boolean includeLocation) {
         return new DefaultLoggerComponentBuilder(this, name, level, includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newRootLogger() {
         return new DefaultRootLoggerComponentBuilder(this, null);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newRootLogger(final boolean includeLocation) {
         return new DefaultRootLoggerComponentBuilder(this, null, includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newRootLogger(final Level level) {
         return new DefaultRootLoggerComponentBuilder(this, level.toString());
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newRootLogger(final Level level, final boolean includeLocation) {
         return new DefaultRootLoggerComponentBuilder(this, level.toString(), includeLocation);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newRootLogger(final String level) {
         return new DefaultRootLoggerComponentBuilder(this, level);
     }
 
+    /** {@inheritDoc} */
     @Override
     public RootLoggerComponentBuilder newRootLogger(final String level, final boolean includeLocation) {
         return new DefaultRootLoggerComponentBuilder(this, level, includeLocation);
     }
 
-    @Override
-    public ConfigurationBuilder<T> setAdvertiser(final String advertiser) {
-        this.advertiser = advertiser;
-        return this;
-    }
+    //
+    // XML SERIALIZATION
+    //
 
-    /**
-     * Set the name of the configuration.
-     *
-     * @param name the name of the {@link Configuration}. By default is {@code "Assembled"}.
-     * @return this builder instance
-     */
-    @Override
-    public ConfigurationBuilder<T> setConfigurationName(final String name) {
-        this.name = name;
-        return this;
+    private String formatXml(final String xml) throws TransformerException, TransformerFactoryConfigurationError {
+        final StringWriter writer = new StringWriter();
+        formatXml(new StreamSource(new StringReader(xml)), new StreamResult(writer));
+        return writer.toString();
     }
 
-    /**
-     * Set the ConfigurationSource.
-     *
-     * @param configurationSource the {@link ConfigurationSource}
-     * @return this builder instance
-     */
-    @Override
-    public ConfigurationBuilder<T> setConfigurationSource(final ConfigurationSource configurationSource) {
-        source = configurationSource;
-        return this;
+    @SuppressFBWarnings(
+            value = {"XXE_DTD_TRANSFORM_FACTORY", "XXE_XSLT_TRANSFORM_FACTORY"},
+            justification = "This method only uses internally generated data.")
+    public static void formatXml(final Source source, final Result result)
+            throws TransformerFactoryConfigurationError, TransformerException {
+        final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+        transformer.setOutputProperty(
+                "{http://xml.apache.org/xslt}indent-amount", Integer.toString(XML_INDENT.length()));
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        transformer.transform(source, result);
     }
 
+    /** {@inheritDoc} */
     @Override
-    public ConfigurationBuilder<T> setMonitorInterval(final String intervalSeconds) {
-        monitorInterval = Integers.parseInt(intervalSeconds);
-        return this;
+    public void writeXmlConfiguration(final OutputStream output) throws IOException {
+        try {
+            final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
+            writeXmlConfiguration(xmlWriter);
+            xmlWriter.close();
+        } catch (final XMLStreamException e) {
+            if (e.getNestedException() instanceof IOException) {
+                throw (IOException) e.getNestedException();
+            }
+            Throwables.rethrow(e);
+        }
     }
 
+    /** {@inheritDoc} */
     @Override
-    public ConfigurationBuilder<T> setPackages(final String packages) {
-        this.packages = packages;
-        return this;
+    public String toXmlConfiguration() {
+        final StringWriter writer = new StringWriter();
+        try {
+            final XMLStreamWriter xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(writer);
+            writeXmlConfiguration(xmlWriter);
+            xmlWriter.close();
+            return formatXml(writer.toString());
+        } catch (final XMLStreamException | TransformerException e) {
+            Throwables.rethrow(e);
+        }
+        return writer.toString();
     }
 
-    @Override
-    public ConfigurationBuilder<T> setShutdownHook(final String flag) {
-        this.shutdownFlag = flag;
-        return this;
+    private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLStreamException {
+        xmlWriter.writeStartDocument();
+        xmlWriter.writeStartElement("Configuration");
+        writeXmlAttributes(xmlWriter);
+        writeXmlSections(xmlWriter);
+        xmlWriter.writeEndElement(); // "Configuration"
+        xmlWriter.writeEndDocument();
     }
 
-    @Override
-    public ConfigurationBuilder<T> setShutdownTimeout(final long timeout, final TimeUnit timeUnit) {
-        this.shutdownTimeoutMillis = timeUnit.toMillis(timeout);
-        return this;
+    /**
+     * Writes the top-level attributes of the main "Configuration" root element.
+     *
+     * @param xmlWriter the XML writer to write to
+     * @throws XMLStreamException if an error occurs while writing the XML document
+     */
+    protected void writeXmlAttributes(final XMLStreamWriter xmlWriter) throws XMLStreamException {
+        writeXmlAttribute(xmlWriter, "name", this.getConfigurationName().orElse(null));
+        writeXmlAttribute(
+                xmlWriter, "status", this.getStatusLevel().map(Level::toString).orElse(null));
+        writeXmlAttribute(xmlWriter, "dest", this.getDestination().orElse(null));
+        writeXmlAttribute(xmlWriter, "packages", this.getPackages().orElse(null));
+        writeXmlAttribute(xmlWriter, "shutdownHook", this.getShutdownHook().orElse(null));
+        writeXmlAttribute(
+                xmlWriter,
+                "shutdownTimeout",
+                this.getShutdownTimeout().map(String::valueOf).orElse(null));
+        writeXmlAttribute(xmlWriter, "advertiser", this.getAdvertiser().orElse(null));
+        writeXmlAttribute(
+                xmlWriter,
+                "monitorInterval",
+                this.getMonitorInterval().map(String::valueOf).orElse(null));
     }
 
-    @Override
-    public ConfigurationBuilder<T> setStatusLevel(final Level level) {
-        this.level = level;
-        return this;
+    /**
+     * Writes the sections (sub-elements) of the main "Configuration" root element.
+     * @param xmlWriter the XML writer to write to
+     * @throws XMLStreamException if an error occurs while writing the XML document
+     */
+    protected void writeXmlSections(final XMLStreamWriter xmlWriter) throws XMLStreamException {
+        writeXmlSection(xmlWriter, properties);
+        writeXmlSection(xmlWriter, scripts);
+        writeXmlSection(xmlWriter, customLevels);
+
+        if (filters.getComponents().size() == 1) {
+            writeXmlComponent(xmlWriter, filters.getComponents().get(0));
+        } else if (filters.getComponents().size() > 1) {
+            writeXmlSection(xmlWriter, filters);
+        }
+
+        writeXmlSection(xmlWriter, appenders);
+        writeXmlSection(xmlWriter, loggers);
     }
 
     /**
-     * @deprecated This method is ineffective and only kept for binary backward compatibility.
+     * Writes an attribute to the given writer.
+     * <p>
+     *     If the provided value is {@code null} no attribute will be written.
+     * </p>
+     * @param xmlWriter the writer to write to
+     * @param name the attribute name
+     * @param value the attribute value (if {@code null} no action will be taken
+     * @throws NullPointerException if either the {@code xmlWriter} or {@code name} attributes are {@code null}
+     * @throws IllegalArgumentException if the {@code name} attribute is blank
+     * @throws XMLStreamException if an error occurs while writing the attribute
      */
-    @Override
-    @Deprecated
-    public ConfigurationBuilder<T> setVerbosity(final String verbosity) {
-        return this;
+    private void writeXmlAttribute(XMLStreamWriter xmlWriter, String name, String value) throws XMLStreamException {
+        Objects.requireNonNull(xmlWriter, "The 'xmlWriter' argument must not be null.");
+        Objects.requireNonNull(name, "The 'name' argument must not be null.");
+        if (name.trim().isEmpty()) {
+            throw new IllegalArgumentException("The 'name' argument must not be blank.");
+        }
+        if (value != null) {
+            xmlWriter.writeAttribute(name, value);
+        }
     }
 
-    @Override
-    public ConfigurationBuilder<T> setDestination(final String destination) {
-        this.destination = destination;
-        return this;
+    private void writeXmlSection(final XMLStreamWriter xmlWriter, final Component component) throws XMLStreamException {
+        if (!component.getAttributes().isEmpty()
+                || !component.getComponents().isEmpty()
+                || component.getValue() != null) {
+            writeXmlComponent(xmlWriter, component);
+        }
     }
 
-    @Override
-    public void setLoggerContext(final LoggerContext loggerContext) {
-        this.loggerContext = loggerContext;
+    private void writeXmlComponent(final XMLStreamWriter xmlWriter, final Component component)
+            throws XMLStreamException {
+        if (!component.getComponents().isEmpty() || component.getValue() != null) {
+            xmlWriter.writeStartElement(component.getPluginType());
+            writeXmlAttributes(xmlWriter, component);
+            for (final Component subComponent : component.getComponents()) {
+                writeXmlComponent(xmlWriter, subComponent);
+            }
+            if (component.getValue() != null) {
+                xmlWriter.writeCharacters(component.getValue());
+            }
+            xmlWriter.writeEndElement();
+        } else {
+            xmlWriter.writeEmptyElement(component.getPluginType());
+            writeXmlAttributes(xmlWriter, component);
+        }
     }
 
-    @Override
-    public ConfigurationBuilder<T> addRootProperty(final String key, final String value) {
-        root.getAttributes().put(key, value);
-        return this;
+    private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component component)
+            throws XMLStreamException {
+        for (final Map.Entry<String, String> attribute :
+                component.getAttributes().entrySet()) {
+            xmlWriter.writeAttribute(attribute.getKey(), attribute.getValue());
+        }
     }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
index c56f92230f3..32a69487d75 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java
@@ -20,7 +20,7 @@
  * @since 2.4
  */
 @Export
-@Version("2.20.2")
+@Version("2.25.0")
 package org.apache.logging.log4j.core.config.builder.impl;
 
 import org.osgi.annotation.bundle.Export;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
index 1563a62e0ec..fae889a28ff 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java
@@ -18,7 +18,7 @@
  * Configuration using Properties files.
  */
 @Export
-@Version("2.20.1")
+@Version("2.25.0")
 package org.apache.logging.log4j.core.config.properties;
 
 import org.osgi.annotation.bundle.Export;
diff --git a/src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml b/src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml
new file mode 100644
index 00000000000..7757a5184f3
--- /dev/null
+++ b/src/changelog/2.25.0/3441_default_configuration_builder_extensibility.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns="https://logging.apache.org/xml/ns"
+       xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+       type="changed">
+  <issue id="3441" link="https://github.com/apache/logging-log4j2/issues/3441"/>
+  <description format="asciidoc">
+    Makes DefaultConfigurationBuilder more extensible and updates BuiltConfiguration
+    to more closely match behavior of other Configuration implementations.
+  </description>
+</entry>

From b8ff8d419530d0e9aa909a3ed443ca90bca23261 Mon Sep 17 00:00:00 2001
From: Jeff Thomas <jeffrey.thomas@t-systems.com>
Date: Mon, 10 Feb 2025 18:05:05 +0100
Subject: [PATCH 2/2] Fix two-pass configuration initialization in
 Configurator1Test (#3441)

---
 .../org/apache/logging/log4j/core/config/Configurator1Test.java  | 1 -
 1 file changed, 1 deletion(-)

diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java
index e46d4cb3ae7..3c500e01af0 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/Configurator1Test.java
@@ -460,7 +460,6 @@ void testRolling() {
 
         builder.add(builder.newRootLogger(Level.DEBUG).add(builder.newAppenderRef("rolling")));
         final Configuration config = builder.build();
-        config.initialize();
         assertNotNull(config.getAppender("rolling"), "No rolling file appender");
         assertEquals("RollingBuilder", config.getName(), "Unexpected Configuration");
         // Initialize the new configuration