diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java
index 8996b9f2f16..fc42abe2937 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java
@@ -34,7 +34,6 @@ class AbstractFilterTest {
     @Test
     void testUnrolledBackwardsCompatible() {
         final ConcreteFilter filter = new ConcreteFilter();
-        final Filter.Result expected = Filter.Result.DENY;
         verifyMethodsWithUnrolledVarargs(filter, Filter.Result.DENY);
 
         filter.testResult = Filter.Result.ACCEPT;
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java
index 4f117a1ccf5..bd3d3f51b20 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java
@@ -54,7 +54,7 @@ void testAddMultipleSimpleFilters() {
         // into a CompositeFilter.class
         filterable.addFilter(filter);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
     }
 
     @Test
@@ -67,7 +67,7 @@ void testAddMultipleEqualSimpleFilter() {
         // into a CompositeFilter.class
         filterable.addFilter(filter);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
     }
 
     @Test
@@ -93,7 +93,7 @@ void testAddMultipleCompositeFilters() {
         // into a CompositeFilter.class
         filterable.addFilter(compositeFilter);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
     }
 
     @Test
@@ -109,7 +109,7 @@ void testAddSimpleFilterAndCompositeFilter() {
         // into a CompositeFilter.class
         filterable.addFilter(compositeFilter);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
     }
 
     @Test
@@ -125,7 +125,7 @@ void testAddCompositeFilterAndSimpleFilter() {
         // into a CompositeFilter.class
         filterable.addFilter(notInCompositeFilterFilter);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
     }
 
     @Test
@@ -170,7 +170,7 @@ void testRemoveSimpleEqualFilterFromMultipleSimpleFilters() {
         filterable.addFilter(filterCopy);
         filterable.removeFilter(filterCopy);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
         filterable.removeFilter(filterCopy);
         assertEquals(filterOriginal, filterable.getFilter());
         filterable.removeFilter(filterOriginal);
@@ -224,7 +224,7 @@ void testRemoveSimpleFilterFromCompositeAndSimpleFilter() {
         // should not remove internal filter of compositeFilter
         filterable.removeFilter(anotherFilter);
         assertInstanceOf(CompositeFilter.class, filterable.getFilter());
-        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
     }
 
     @Test
@@ -247,9 +247,9 @@ void testRemoveFiltersFromComposite() {
 
         filterable.addFilter(compositeFilter);
         filterable.addFilter(anotherFilter);
-        assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
         filterable.removeFilter(filter1);
-        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+        assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
         filterable.removeFilter(filter2);
         assertSame(anotherFilter, filterable.getFilter());
     }
@@ -274,11 +274,7 @@ public boolean equals(final Object o) {
 
             final EqualFilter that = (EqualFilter) o;
 
-            if (key != null ? !key.equals(that.key) : that.key != null) {
-                return false;
-            }
-
-            return true;
+            return key != null ? key.equals(that.key) : that.key == null;
         }
 
         @Override
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java
index 671d998258b..3bf96c607fd 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/RegexFilterTest.java
@@ -19,6 +19,9 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -37,19 +40,23 @@
 class RegexFilterTest {
     @BeforeAll
     static void before() {
-        StatusLogger.getLogger().setLevel(Level.OFF);
+        StatusLogger.getLogger().getFallbackListener().setLevel(Level.OFF);
     }
 
     @Test
     void testRegexFilterDoesNotThrowWithAllTheParametersExceptRegexEqualNull() {
         assertDoesNotThrow(() -> {
-            RegexFilter.createFilter(".* test .*", null, null, null, null);
+            RegexFilter.newBuilder().setRegex(".* test .*").build();
         });
     }
 
     @Test
     void testThresholds() throws Exception {
-        RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null);
+        RegexFilter filter = RegexFilter.newBuilder()
+                .setRegex(".* test .*")
+                .setUseRawMsg(false)
+                .build();
+        assertNotNull(filter);
         filter.start();
         assertTrue(filter.isStarted());
         assertSame(
@@ -65,7 +72,7 @@ void testThresholds() throws Exception {
                 .setMessage(new SimpleMessage("test")) //
                 .build();
         assertSame(Filter.Result.DENY, filter.filter(event));
-        filter = RegexFilter.createFilter(null, null, false, null, null);
+        filter = RegexFilter.newBuilder().build();
         assertNull(filter);
     }
 
@@ -82,9 +89,17 @@ void testDotAllPattern() throws Exception {
     }
 
     @Test
-    void testNoMsg() throws Exception {
-        final RegexFilter filter = RegexFilter.createFilter(".* test .*", null, false, null, null);
+    void testNoMsg() {
+
+        final RegexFilter filter = RegexFilter.newBuilder()
+                .setRegex(".* test .*")
+                .setUseRawMsg(false)
+                .build();
+
+        assertNotNull(filter);
+
         filter.start();
+
         assertTrue(filter.isStarted());
         assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Object) null, null));
         assertSame(Filter.Result.DENY, filter.filter(null, Level.DEBUG, null, (Message) null, null));
@@ -92,28 +107,105 @@ void testNoMsg() throws Exception {
     }
 
     @Test
-    void testParameterizedMsg() throws Exception {
+    void testParameterizedMsg() {
         final String msg = "params {} {}";
         final Object[] params = {"foo", "bar"};
 
         // match against raw message
-        final RegexFilter rawFilter = RegexFilter.createFilter(
-                "params \\{\\} \\{\\}",
-                null,
-                true, // useRawMsg
-                Result.ACCEPT,
-                Result.DENY);
+        final RegexFilter rawFilter = RegexFilter.newBuilder()
+                .setRegex("params \\{\\} \\{\\}")
+                .setUseRawMsg(true)
+                .setOnMatch(Result.ACCEPT)
+                .setOnMismatch(Result.DENY)
+                .build();
+
+        assertNotNull(rawFilter);
+
         final Result rawResult = rawFilter.filter(null, null, null, msg, params);
         assertThat(rawResult, equalTo(Result.ACCEPT));
 
         // match against formatted message
-        final RegexFilter fmtFilter = RegexFilter.createFilter(
-                "params foo bar",
-                null,
-                false, // useRawMsg
-                Result.ACCEPT,
-                Result.DENY);
+        final RegexFilter fmtFilter = RegexFilter.newBuilder()
+                .setRegex("params foo bar")
+                .setUseRawMsg(false)
+                .setOnMatch(Result.ACCEPT)
+                .setOnMismatch(Result.DENY)
+                .build();
+
+        assertNotNull(fmtFilter);
+
         final Result fmtResult = fmtFilter.filter(null, null, null, msg, params);
         assertThat(fmtResult, equalTo(Result.ACCEPT));
     }
+
+    /**
+     * A builder with no 'regex' expression should both be invalid and return null on 'build()'.
+     */
+    @Test
+    void testWithValidRegex() {
+
+        final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores
+
+        final RegexFilter.Builder builder = RegexFilter.newBuilder()
+                .setRegex(regex)
+                .setUseRawMsg(false)
+                .setOnMatch(Result.ACCEPT)
+                .setOnMismatch(Result.DENY);
+
+        final RegexFilter filter = builder.build();
+
+        assertNotNull(filter);
+
+        assertEquals(Result.ACCEPT, filter.filter("Hello_123"));
+
+        assertEquals(Result.DENY, filter.filter("Hello@123"));
+
+        assertEquals(regex, filter.getRegex());
+    }
+
+    @Test
+    void testRegexFilterGetters() {
+
+        final String regex = "^[a-zA-Z0-9_]+$"; // matches alphanumeric with underscores
+
+        final RegexFilter filter = RegexFilter.newBuilder()
+                .setRegex(regex)
+                .setUseRawMsg(false)
+                .setOnMatch(Result.ACCEPT)
+                .setOnMismatch(Result.DENY)
+                .build();
+
+        assertNotNull(filter);
+
+        assertEquals(regex, filter.getRegex());
+        assertFalse(filter.isUseRawMessage());
+        assertEquals(Result.ACCEPT, filter.getOnMatch());
+        assertEquals(Result.DENY, filter.getOnMismatch());
+        assertNotNull(filter.getPattern());
+        assertEquals(regex, filter.getPattern().pattern());
+    }
+
+    /**
+     * A builder with no 'regex' expression should both be invalid and return null on 'build()'.
+     */
+    @Test
+    void testBuilderWithoutRegexNotValid() {
+
+        final RegexFilter.Builder builder = RegexFilter.newBuilder();
+
+        assertNull(builder.build());
+    }
+
+    /**
+     * A builder with an invalid 'regex' expression should return null on 'build()'.
+     */
+    @Test
+    void testBuilderWithInvalidRegexNotValid() {
+
+        final RegexFilter.Builder builder = RegexFilter.newBuilder();
+
+        builder.setRegex("[a-z");
+
+        assertNull(builder.build());
+    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java
index 397390bcbc3..05b1e6b275b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java
@@ -16,6 +16,8 @@
  */
 package org.apache.logging.log4j.core.filter;
 
+import java.util.Objects;
+import java.util.Optional;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
 import org.apache.logging.log4j.core.AbstractLifeCycle;
@@ -43,16 +45,30 @@ public abstract static class AbstractFilterBuilder<B extends AbstractFilterBuild
         public static final String ATTR_ON_MISMATCH = "onMismatch";
         public static final String ATTR_ON_MATCH = "onMatch";
 
+        /**
+         * The action to perform when a match occurs.
+         */
         @PluginBuilderAttribute(ATTR_ON_MATCH)
-        private Result onMatch = Result.NEUTRAL;
+        protected Result onMatch = Result.NEUTRAL;
 
+        /**
+         * The action to perform when a mismatch occurs.
+         */
         @PluginBuilderAttribute(ATTR_ON_MISMATCH)
-        private Result onMismatch = Result.DENY;
+        protected Result onMismatch = Result.DENY;
 
+        /**
+         * Returns the action to apply when a match occurs
+         * @return the match result
+         */
         public Result getOnMatch() {
             return onMatch;
         }
 
+        /**
+         * Returns the action to apply when a mismatch occurs
+         * @return the mismatch result
+         */
         public Result getOnMismatch() {
             return onMismatch;
         }
@@ -110,6 +126,19 @@ protected AbstractFilter(final Result onMatch, final Result onMismatch) {
         this.onMismatch = onMismatch == null ? Result.DENY : onMismatch;
     }
 
+    /**
+     * Constructs a new instance configured by the given builder
+     * @param builder the builder
+     * @throws NullPointerException if the builder argument is {@code null}
+     */
+    protected AbstractFilter(final AbstractFilterBuilder<?> builder) {
+
+        Objects.requireNonNull(builder, "The 'builder' argument cannot be null.");
+
+        this.onMatch = Optional.ofNullable(builder.onMatch).orElse(Result.NEUTRAL);
+        this.onMismatch = Optional.ofNullable(builder.onMismatch).orElse(Result.DENY);
+    }
+
     @Override
     protected boolean equalsImpl(final Object obj) {
         if (this == obj) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
index 62d41b31f59..839089b35bb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
@@ -19,7 +19,7 @@
 import java.lang.reflect.Field;
 import java.util.Arrays;
 import java.util.Comparator;
-import java.util.regex.Matcher;
+import java.util.Objects;
 import java.util.regex.Pattern;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Marker;
@@ -29,68 +29,210 @@
 import org.apache.logging.log4j.core.config.Node;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.util.Assert;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MessageFormatMessage;
 import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.apache.logging.log4j.message.StringFormattedMessage;
 import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.apache.logging.log4j.util.Strings;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
 
 /**
- * A filter that matches the given regular expression pattern against messages.
+ * This filter returns the {@code onMatch} result if the message exactly matches the configured
+ * "{@code regex}" regular-expression pattern; otherwise, it returns the {@code onMismatch} result.
  */
 @Plugin(name = "RegexFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
+@NullMarked
 public final class RegexFilter extends AbstractFilter {
 
-    private static final int DEFAULT_PATTERN_FLAGS = 0;
+    /** The pattern compiled from the regular-expression. */
     private final Pattern pattern;
+
+    /** Flag: if {@code true} use message format-pattern / field for the match target. */
     private final boolean useRawMessage;
 
-    private RegexFilter(final boolean raw, final Pattern pattern, final Result onMatch, final Result onMismatch) {
-        super(onMatch, onMismatch);
-        this.pattern = pattern;
-        this.useRawMessage = raw;
+    /**
+     * Constructs a new {@code RegexFilter} configured by the given builder.
+     * @param builder the builder
+     * @throws IllegalArgumentException if the regular expression is not configured or cannot be compiled to a pattern
+     */
+    private RegexFilter(final Builder builder) {
+
+        super(builder);
+
+        if (Strings.isBlank(builder.regex)) {
+            throw new IllegalArgumentException("The 'regex' attribute must not be null or empty.");
+        }
+
+        this.useRawMessage = Boolean.TRUE.equals(builder.useRawMsg);
+
+        try {
+            this.pattern = Pattern.compile(builder.regex);
+        } catch (final Exception ex) {
+            throw new IllegalArgumentException("Unable to compile regular expression: '" + builder.regex + "'.", ex);
+        }
+    }
+
+    /**
+     * Returns the compiled regular-expression pattern.
+     * @return the pattern (will never be {@code null}
+     */
+    public Pattern getPattern() {
+        return this.pattern;
+    }
+
+    /**
+     * Returns the regular-expression.
+     * @return the regular-expression (it may be an empty string but never {@code null})
+     */
+    public String getRegex() {
+        return this.pattern.pattern();
+    }
+
+    /**
+     * Returns whether the raw-message should be used.
+     * @return {@code true} if the raw message should be used; otherwise, {@code false}
+     */
+    public boolean isUseRawMessage() {
+        return this.useRawMessage;
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     *   This implementation performs the filter evaluation against the given message formatted with
+     *   the given parameters.
+     * </p>
+     * <p>
+     *   The following method arguments are ignored by this filter method implementation:
+     *   <ul>
+     *     <li>{@code logger}</li>
+     *     <li>{@code level}</li>
+     *     <li>{@code marker}</li>
+     *   </ul>
+     * </p>
+     */
     @Override
     public Result filter(
-            final Logger logger, final Level level, final Marker marker, final String msg, final Object... params) {
-        if (useRawMessage || params == null || params.length == 0) {
-            return filter(msg);
-        }
-        return filter(ParameterizedMessage.format(msg, params));
+            final @Nullable Logger logger,
+            final @Nullable Level level,
+            final @Nullable Marker marker,
+            final @Nullable String msg,
+            final @Nullable Object @Nullable ... params) {
+
+        return (useRawMessage || params == null || params.length == 0)
+                ? filter(msg)
+                : filter(ParameterizedMessage.format(msg, params));
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     *   This implementation performs the filter evaluation against the given message.
+     * </p>
+     * <p>
+     *   The following method arguments are ignored by this filter method implementation:
+     *   <ul>
+     *     <li>{@code logger}</li>
+     *     <li>{@code level}</li>
+     *     <li>{@code marker}</li>
+     *     <li>{@code throwable}</li>
+     *   </ul>
+     * </p>
+     */
     @Override
     public Result filter(
-            final Logger logger, final Level level, final Marker marker, final Object msg, final Throwable t) {
-        if (msg == null) {
-            return onMismatch;
-        }
-        return filter(msg.toString());
+            final @Nullable Logger logger,
+            final @Nullable Level level,
+            final @Nullable Marker marker,
+            final @Nullable Object message,
+            final @Nullable Throwable throwable) {
+
+        return (message == null) ? this.onMismatch : filter(message.toString());
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     *   This implementation performs the filter evaluation against the given message.
+     * </p>
+     * <p>
+     *   The following method arguments are ignored by this filter method implementation:
+     *   <ul>
+     *     <li>{@code logger}</li>
+     *     <li>{@code level}</li>
+     *     <li>{@code marker}</li>
+     *     <li>{@code throwable}</li>
+     *   </ul>
+     * </p>
+     */
     @Override
     public Result filter(
-            final Logger logger, final Level level, final Marker marker, final Message msg, final Throwable t) {
-        if (msg == null) {
-            return onMismatch;
-        }
-        final String text = targetMessageTest(msg);
-        return filter(text);
+            final @Nullable Logger logger,
+            final @Nullable Level level,
+            final @Nullable Marker marker,
+            final @Nullable Message message,
+            final @Nullable Throwable throwable) {
+        return (message == null) ? this.onMismatch : filter(getMessageTextByType(message));
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @throws NullPointerException if the {@code event} argument is {@code null}
+     */
     @Override
     public Result filter(final LogEvent event) {
-        final String text = targetMessageTest(event.getMessage());
-        return filter(text);
+        Objects.requireNonNull(event, "The 'event' argument must not be null.");
+        return filter(getMessageTextByType(event.getMessage()));
+    }
+
+    /**
+     * Apply the filter to the given message and return the {@code onMatch} result if the <i>entire</i>
+     * message matches the configured regex pattern; otherwise, {@code onMismatch}.
+     * <p>
+     *   If the given '{@code msg}' is {@code null} the configured {@code onMismatch} result will be returned.
+     * </p>
+     * @param msg the message
+     * @return the {@code onMatch} result if the pattern matches; otherwise, the {@code onMismatch} result
+     */
+    public Result filter(final @Nullable String msg) {
+        return (msg != null && pattern.matcher(msg).matches()) ? onMatch : onMismatch;
     }
 
-    // While `Message#getFormat()` is broken in general, it still makes sense for certain types.
-    // Hence, suppress the deprecation warning.
+    /**
+     * Tests the filter pattern against the given Log4j {@code Message}.
+     * <p>
+     *   If the raw-message flag is enabled and message is an instance of the following, the raw message format
+     *   will be returned.
+     * </p>
+     * <ul>
+     *   <li>{@link MessageFormatMessage}</li>
+     *   <li>{@link ParameterizedMessage}</li>
+     *   <li>{@link StringFormattedMessage}</li>
+     *   <li>{@link StructuredDataMessage}</li>
+     * </ul>
+     * <p>
+     *   If the '{@code useRawMessage}' flag is disabled <i>OR</i> the message is not one of the above
+     *   implementations, the message's formatted message will be returned.
+     * </p>
+     * <h3>Developer Note</h3>
+     * <p>
+     * While `Message#getFormat()` is broken in general, it still makes sense for certain types.
+     * Hence, suppress the deprecation warning.
+     * </p>
+     *
+     * @param message the message
+     * @return the target message based on configuration and message-type
+     */
     @SuppressWarnings("deprecation")
-    private String targetMessageTest(final Message message) {
+    private String getMessageTextByType(final Message message) {
         return useRawMessage
                         && (message instanceof ParameterizedMessage
                                 || message instanceof StringFormattedMessage
@@ -100,59 +242,191 @@ private String targetMessageTest(final Message message) {
                 : message.getFormattedMessage();
     }
 
-    private Result filter(final String msg) {
-        if (msg == null) {
-            return onMismatch;
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "useRawMessage=" + useRawMessage + ", pattern=" + pattern.toString();
+    }
+
+    /**
+     * Creates a new builder instance.
+     * @return the new builder instance
+     */
+    @PluginBuilderFactory
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /**
+     * A {@link RegexFilter} builder instance.
+     */
+    public static final class Builder extends AbstractFilterBuilder<RegexFilter.Builder>
+            implements org.apache.logging.log4j.core.util.Builder<RegexFilter> {
+
+        /* NOTE: LOG4J-3086 - No patternFlags in builder - this functionality has been deprecated/removed. */
+
+        /**
+         * The regular expression to match.
+         */
+        @PluginBuilderAttribute
+        @Required(message = "No 'regex' provided for RegexFilter")
+        private @Nullable String regex;
+
+        /**
+         * If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage},
+         * and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage},
+         * the message field will be used as the match target.
+         */
+        @PluginBuilderAttribute
+        private @Nullable Boolean useRawMsg;
+
+        /** Private constructor. */
+        private Builder() {
+            super();
+        }
+
+        /**
+         * Sets the regular-expression.
+         *
+         * @param regex the regular-expression
+         * @return this builder
+         */
+        public Builder setRegex(final String regex) {
+            this.regex = Assert.requireNonEmpty(regex, "The 'regex' attribute must not be null or empty.");
+            return this;
+        }
+
+        /**
+         * Sets the use raw msg flag.
+         *
+         * @param useRawMsg {@code true} if the message format-patter/field will be used as match target;
+         *                  otherwise, {@code false}
+         * @return this builder
+         */
+        public Builder setUseRawMsg(final boolean useRawMsg) {
+            this.useRawMsg = useRawMsg;
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public boolean isValid() {
+            return (Strings.isNotEmpty(this.regex));
+        }
+
+        /**
+         * Builds and returns a {@link RegexFilter} instance configured by this builder.
+         *
+         * @return the created {@link RegexFilter} or {@code null} if the builder is misconfigured
+         */
+        @Override
+        public @Nullable RegexFilter build() {
+
+            // validate the "regex" attribute
+            if (Strings.isEmpty(this.regex)) {
+                LOGGER.error("Unable to create RegexFilter: The 'regex' attribute be set to a non-empty String.");
+                return null;
+            }
+
+            // build with *safety* to not throw exceptions
+            try {
+                return new RegexFilter(this);
+            } catch (final Exception ex) {
+                LOGGER.error("Unable to create RegexFilter. {}", ex.getMessage(), ex);
+                return null;
+            }
         }
-        final Matcher m = pattern.matcher(msg);
-        return m.matches() ? onMatch : onMismatch;
     }
 
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("useRaw=").append(useRawMessage);
-        sb.append(", pattern=").append(pattern.toString());
-        return sb.toString();
+    /*
+     * DEPRECATIONS:
+     * The constructor/fields/methods below have been deprecated.
+     * - the 'create***' factory methods should no longer be used - use the builder instead
+     * - pattern-flags should now be passed via the regular expression itself
+     */
+
+    /**
+     * @deprecated pattern flags have been deprecated - they can just be included in the regex-expression.
+     */
+    @Deprecated
+    private static final int DEFAULT_PATTERN_FLAGS = 0;
+
+    /**
+     * @deprecated - pattern flags no longer supported.
+     */
+    @Deprecated
+    private String[] patternFlags = new String[0];
+
+    /**
+     * @deprecated use {@link RegexFilter.Builder} instead
+     */
+    @Deprecated
+    @SuppressWarnings("MagicConstant")
+    private RegexFilter(
+            final String regex,
+            final boolean useRawMessage,
+            final @Nullable String @Nullable [] patternFlags,
+            final @Nullable Result onMatch,
+            final @Nullable Result onMismatch) {
+        super(onMatch, onMismatch);
+        Objects.requireNonNull(regex, "The 'regex' argument must be provided for RegexFilter");
+        this.patternFlags = patternFlags == null ? new String[0] : patternFlags.clone();
+        try {
+            int flags = toPatternFlags(this.patternFlags);
+            this.pattern = Pattern.compile(regex, flags);
+        } catch (final Exception ex) {
+            throw new IllegalArgumentException("Unable to compile regular expression: '" + regex + "'.", ex);
+        }
+        this.useRawMessage = useRawMessage;
+    }
+
+    /**
+     * Returns the pattern-flags applied to the regular-expression when compiling the pattern.
+     *
+     * @return the pattern-flags (maybe empty but never {@code null}
+     * @deprecated pattern-flags are no longer supported
+     */
+    @Deprecated
+    public String[] getPatternFlags() {
+        return this.patternFlags.clone();
     }
 
     /**
      * Creates a Filter that matches a regular expression.
      *
-     * @param regex
-     *        The regular expression to match.
-     * @param patternFlags
-     *        An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag.
-     * @param useRawMsg
-     *        If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage}, and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage}, the message field will be used as the match target.
-     * @param match
-     *        The action to perform when a match occurs.
-     * @param mismatch
-     *        The action to perform when a mismatch occurs.
+     * @param regex        The regular expression to match.
+     * @param patternFlags An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag.
+     *                     (no longer used - pattern flags can be embedded in regex-expression.
+     * @param useRawMsg    If {@code true}, for {@link ParameterizedMessage}, {@link StringFormattedMessage},
+     *                     and {@link MessageFormatMessage}, the message format pattern; for {@link StructuredDataMessage},
+     *                     the message field will be used as the match target.
+     * @param match        The action to perform when a match occurs.
+     * @param mismatch     The action to perform when a mismatch occurs.
      * @return The RegexFilter.
-     * @throws IllegalAccessException  When there is no access to the definition of the specified member.
+     * @throws IllegalAccessException   When there is no access to the definition of the specified member.
      * @throws IllegalArgumentException When passed an illegal or inappropriate argument.
+     * @deprecated use {@link #newBuilder} to instantiate builder
      */
-    // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
-    @PluginFactory
+    @Deprecated
     public static RegexFilter createFilter(
             // @formatter:off
             @PluginAttribute("regex") final String regex,
-            @PluginElement("PatternFlags") final String[] patternFlags,
-            @PluginAttribute("useRawMsg") final Boolean useRawMsg,
-            @PluginAttribute("onMatch") final Result match,
-            @PluginAttribute("onMismatch") final Result mismatch)
+            @PluginElement("PatternFlags") final String @Nullable [] patternFlags,
+            @PluginAttribute("useRawMsg") final @Nullable Boolean useRawMsg,
+            @PluginAttribute("onMatch") final @Nullable Result match,
+            @PluginAttribute("onMismatch") final @Nullable Result mismatch)
             // @formatter:on
             throws IllegalArgumentException, IllegalAccessException {
-        if (regex == null) {
-            LOGGER.error("A regular expression must be provided for RegexFilter");
-            return null;
-        }
-        return new RegexFilter(
-                Boolean.TRUE.equals(useRawMsg), Pattern.compile(regex, toPatternFlags(patternFlags)), match, mismatch);
+
+        // LOG4J-3086 - pattern-flags can be embedded in RegEx expression
+        Objects.requireNonNull(regex, "The 'regex' argument must not be null.");
+
+        return new RegexFilter(regex, Boolean.TRUE.equals(useRawMsg), patternFlags, match, mismatch);
     }
 
-    private static int toPatternFlags(final String[] patternFlags)
+    /** @deprecated pattern flags have been deprecated - they can just be included in the regex-expression. */
+    @Deprecated
+    private static int toPatternFlags(final String @Nullable [] patternFlags)
             throws IllegalArgumentException, IllegalAccessException {
         if (patternFlags == null || patternFlags.length == 0) {
             return DEFAULT_PATTERN_FLAGS;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
index 10bc1a9f52e..8a772dc7330 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
@@ -44,6 +44,10 @@ public interface Builder<T> {
      */
     T build();
 
+    /**
+     * Validates that the builder is properly configured to build.
+     * @return {@code true} if the builder configuration is valid; otherwise, {@code false}
+     */
     default boolean isValid() {
         return PluginBuilder.validateFields(this, getErrorPrefix());
     }
diff --git a/src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml b/src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml
new file mode 100644
index 00000000000..0e61653f85c
--- /dev/null
+++ b/src/changelog/.2.x.x/3086_change_RegexFilter_remove_patternflags_from_PluginFactory.xml
@@ -0,0 +1,10 @@
+<?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="fixed">
+  <issue id="3086" link="https://github.com/apache/logging-log4j2/issues/3086"/>
+  <description format="asciidoc">
+    Removed 'patternFlags' @PluginAttribute from RegexFilter @PluginFactory createFilter.
+  </description>
+</entry>