Repository: kroxylicious/kroxylicious
Pull request #653 (2023-10-03 01:35:15)

Remove FilterChainFactory's dependence on Configuration
### Type of change

- Refactoring

### Description

Removes `FilterChainFactory`'s dependency on `Configuration`. A FCF should depend only on a configured list of filters. It doesn't need to know where those originated from. In the future this might allow us to support multiple chains of filters. 

Furthermore the FCF can validate those filters on instantiation, and throw a new useful exception type. 



sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=kroxylicious_kroxylicious&pullRequest=653)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [2 Code Smells](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL)

[![66.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '66.7%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list) [66.7% Coverage](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list)



![idea](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png 'idea') Catch issues before they fail your Quality Gate with our IDE extension ![sonarlint](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png 'sonarlint') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarcloud-welcome)
sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=kroxylicious_kroxylicious&pullRequest=653)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL)

[![66.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '66.7%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list) [66.7% Coverage](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list)



![idea](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/light_bulb-16px.png 'idea') Catch issues before they fail your Quality Gate with our IDE extension ![sonarlint](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/sonarlint-16px.png 'sonarlint') [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarcloud-welcome)
sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=kroxylicious_kroxylicious&pullRequest=653)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL)

[![91.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '91.7%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list) [91.7% Coverage](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=kroxylicious_kroxylicious&pullRequest=653)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=kroxylicious_kroxylicious&pullRequest=653&resolved=false&types=CODE_SMELL)

[![91.7%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90-16px.png '91.7%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list) [91.7% Coverage](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=kroxylicious_kroxylicious&pullRequest=653&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/kroxylicious/src/main/java/io/kroxylicious/proxy/KafkaProxy.java b/kroxylicious/src/main/java/io/kroxylicious/proxy/KafkaProxy.java
index 7392265c..495f677d 100644
--- a/kroxylicious/src/main/java/io/kroxylicious/proxy/KafkaProxy.java
+++ b/kroxylicious/src/main/java/io/kroxylicious/proxy/KafkaProxy.java
@@ -8,11 +8,10 @@ package io.kroxylicious.proxy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -77,7 +76,7 @@ public final class KafkaProxy implements AutoCloseable {
     private Channel metricsChannel;
 
     public KafkaProxy(Configuration config) {
-        this.config = config;
+        this.config = Objects.requireNonNull(config);
         this.virtualClusters = config.virtualClusterModel();
         this.adminHttpConfig = config.adminHttpConfig();
         this.micrometerConfig = config.getMicrometer();
@@ -104,17 +103,14 @@ public final class KafkaProxy implements AutoCloseable {
 
         maybeStartMetricsListener(adminEventGroup, meterRegistries);
 
-        var tlsServerBootstrap = buildServerBootstrap(serverEventGroup, new KafkaProxyInitializer(config, true, endpointRegistry, endpointRegistry, false, Map.of()));
-        var plainServerBootstrap = buildServerBootstrap(serverEventGroup, new KafkaProxyInitializer(config, false, endpointRegistry, endpointRegistry, false, Map.of()));
+        var filterChainFactory = new FilterChainFactory(config.filters());
+        var tlsServerBootstrap = buildServerBootstrap(serverEventGroup,
+                new KafkaProxyInitializer(filterChainFactory, true, endpointRegistry, endpointRegistry, false, Map.of()));
+        var plainServerBootstrap = buildServerBootstrap(serverEventGroup,
+                new KafkaProxyInitializer(filterChainFactory, false, endpointRegistry, endpointRegistry, false, Map.of()));
 
         bindingOperationProcessor.start(plainServerBootstrap, tlsServerBootstrap);
 
-        final Set<String> invalidFilters = FilterChainFactory.validateFilterConfiguration(config.filters());
-        if (!invalidFilters.isEmpty()) {
-            throw new IllegalStateException(invalidFilters.stream()
-                    .collect(Collectors.joining(", ", "Missing required config for [", "]")));
-        }
-
         // TODO: startup/shutdown should return a completionstage
         CompletableFuture.allOf(virtualClusters.stream().map(vc -> endpointRegistry.registerVirtualCluster(vc).toCompletableFuture()).toArray(CompletableFuture[]::new))
                 .join();
@@ -247,4 +243,4 @@ public final class KafkaProxy implements AutoCloseable {
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/kroxylicious/src/main/java/io/kroxylicious/proxy/bootstrap/FilterChainFactory.java b/kroxylicious/src/main/java/io/kroxylicious/proxy/bootstrap/FilterChainFactory.java
index 879725de..4c12365e 100644
--- a/kroxylicious/src/main/java/io/kroxylicious/proxy/bootstrap/FilterChainFactory.java
+++ b/kroxylicious/src/main/java/io/kroxylicious/proxy/bootstrap/FilterChainFactory.java
@@ -7,15 +7,14 @@ package io.kroxylicious.proxy.bootstrap;
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Objects;
-import java.util.Set;
+import java.util.Optional;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
-import io.kroxylicious.proxy.config.Configuration;
 import io.kroxylicious.proxy.config.FilterDefinition;
 import io.kroxylicious.proxy.filter.FilterAndInvoker;
 import io.kroxylicious.proxy.filter.FilterCreationContext;
+import io.kroxylicious.proxy.filter.InvalidFilterConfigurationException;
 import io.kroxylicious.proxy.internal.filter.NettyFilterContext;
 import io.kroxylicious.proxy.service.FilterFactoryManager;
 
@@ -25,39 +24,39 @@ import io.kroxylicious.proxy.service.FilterFactoryManager;
  * New instances are created during initialization of a downstream channel.
  */
 public class FilterChainFactory {
-    private final Configuration config;
+    private final List<FilterDefinition> filterDefinitions;
 
-    public FilterChainFactory(Configuration config) {
-        this.config = Objects.requireNonNull(config);
+    public FilterChainFactory(List<FilterDefinition> filterDefinitions) {
+        validateFilterDefinitions(filterDefinitions);
+        this.filterDefinitions = Optional.ofNullable(filterDefinitions).orElse(List.of());
     }
 
     /**
      * Validate that a collection of FilterDefinitions contains all the required configuration to configure the filter.
      * @param filterDefinitions the candidates to validate.
-     * @return the Set of filter names which fail validation.
+     * @throws InvalidFilterConfigurationException If there are problems.
      */
-    public static Set<String> validateFilterConfiguration(Collection<FilterDefinition> filterDefinitions) {
+    public static void validateFilterDefinitions(Collection<FilterDefinition> filterDefinitions) {
         if (filterDefinitions == null || filterDefinitions.isEmpty()) {
-            return Set.of();
+            return;
         }
-        return filterDefinitions.stream()
+        var invalidFilters = filterDefinitions.stream()
                 .filter(Predicate.not(FilterDefinition::isDefinitionValid))
                 .map(FilterDefinition::type)
                 .collect(Collectors.toSet());
+        if (!invalidFilters.isEmpty()) {
+            throw new InvalidFilterConfigurationException(invalidFilters.stream()
+                    .collect(Collectors.joining(", ", "Invalid config for [", "]")));
+        }
     }
 
     /**
-     * Create a new chain of filter instances
+     * Creates and returns a new chain of filter instances.
      *
      * @return the new chain.
      */
     public List<FilterAndInvoker> createFilters(NettyFilterContext context) {
-        List<FilterDefinition> filters = config.filters();
-        if (filters == null || filters.isEmpty()) {
-            return List.of();
-        }
-        validateFilterConfiguration(filters);
-        return filters
+        return filterDefinitions
                 .stream()
                 .map(f -> {
                     FilterCreationContext wrap = context;
diff --git a/kroxylicious/src/main/java/io/kroxylicious/proxy/internal/KafkaProxyInitializer.java b/kroxylicious/src/main/java/io/kroxylicious/proxy/internal/KafkaProxyInitializer.java
index 5e2d2864..ea828f0f 100644
--- a/kroxylicious/src/main/java/io/kroxylicious/proxy/internal/KafkaProxyInitializer.java
+++ b/kroxylicious/src/main/java/io/kroxylicious/proxy/internal/KafkaProxyInitializer.java
@@ -27,7 +27,6 @@ import io.netty.handler.ssl.SslContext;
 import io.netty.util.concurrent.Future;
 
 import io.kroxylicious.proxy.bootstrap.FilterChainFactory;
-import io.kroxylicious.proxy.config.Configuration;
 import io.kroxylicious.proxy.filter.FilterAndInvoker;
 import io.kroxylicious.proxy.internal.codec.KafkaRequestDecoder;
 import io.kroxylicious.proxy.internal.codec.KafkaResponseEncoder;
@@ -49,9 +48,9 @@ public class KafkaProxyInitializer extends ChannelInitializer<SocketChannel> {
     private final boolean tls;
     private final VirtualClusterBindingResolver virtualClusterBindingResolver;
     private final EndpointReconciler endpointReconciler;
-    private final Configuration config;
+    private final FilterChainFactory filterChainFactory;
 
-    public KafkaProxyInitializer(Configuration config, boolean tls,
+    public KafkaProxyInitializer(FilterChainFactory filterChainFactory, boolean tls,
                                  VirtualClusterBindingResolver virtualClusterBindingResolver, EndpointReconciler endpointReconciler,
                                  boolean haproxyProtocol,
                                  Map<KafkaAuthnHandler.SaslMechanism, AuthenticateCallbackHandler> authnMechanismHandlers) {
@@ -60,7 +59,7 @@ public class KafkaProxyInitializer extends ChannelInitializer<SocketChannel> {
         this.authnHandlers = authnMechanismHandlers != null ? authnMechanismHandlers : Map.of();
         this.tls = tls;
         this.virtualClusterBindingResolver = virtualClusterBindingResolver;
-        this.config = config;
+        this.filterChainFactory = filterChainFactory;
     }
 
     @Override
@@ -173,7 +172,6 @@ public class KafkaProxyInitializer extends ChannelInitializer<SocketChannel> {
 
         ApiVersionsServiceImpl apiVersionService = new ApiVersionsServiceImpl();
         var frontendHandler = new KafkaProxyFrontendHandler(context -> {
-            var filterChainFactory = new FilterChainFactory(config);
             List<FilterAndInvoker> apiVersionFilters = dp.isAuthenticationOffloadEnabled() ? List.of()
                     : FilterAndInvoker.build(new ApiVersionsIntersectFilter(apiVersionService));
             List<FilterAndInvoker> customProtocolFilters = filterChainFactory.createFilters(new NettyFilterContext(ch.eventLoop()));


Test patch:
diff --git a/kroxylicious/src/test/java/io/kroxylicious/proxy/KafkaProxyTest.java b/kroxylicious/src/test/java/io/kroxylicious/proxy/KafkaProxyTest.java
index 07f7563c..7aec1084 100644
--- a/kroxylicious/src/test/java/io/kroxylicious/proxy/KafkaProxyTest.java
+++ b/kroxylicious/src/test/java/io/kroxylicious/proxy/KafkaProxyTest.java
@@ -15,6 +15,7 @@ import org.junit.jupiter.params.provider.MethodSource;
 
 import io.kroxylicious.proxy.config.ConfigParser;
 import io.kroxylicious.proxy.config.Configuration;
+import io.kroxylicious.proxy.filter.InvalidFilterConfigurationException;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -40,8 +41,8 @@ class KafkaProxyTest {
                        - type: ProduceRequestTransformationFilter
                 """;
         try (var kafkaProxy = new KafkaProxy(new ConfigParser().parseConfiguration(config))) {
-            assertThatThrownBy(kafkaProxy::startup).isInstanceOf(IllegalStateException.class)
-                    .hasMessage("Missing required config for [ProduceRequestTransformationFilter]");
+            assertThatThrownBy(kafkaProxy::startup).isInstanceOf(InvalidFilterConfigurationException.class)
+                    .hasMessage("Invalid config for [ProduceRequestTransformationFilter]");
         }
     }
 
@@ -123,4 +124,4 @@ class KafkaProxyTest {
         });
         assertThat(illegalStateException).hasStackTraceContaining(expectedMessage);
     }
-}
\ No newline at end of file
+}
diff --git a/kroxylicious/src/test/java/io/kroxylicious/proxy/bootstrap/FilterChainFactoryTest.java b/kroxylicious/src/test/java/io/kroxylicious/proxy/bootstrap/FilterChainFactoryTest.java
index cf8fd03c..4781453d 100644
--- a/kroxylicious/src/test/java/io/kroxylicious/proxy/bootstrap/FilterChainFactoryTest.java
+++ b/kroxylicious/src/test/java/io/kroxylicious/proxy/bootstrap/FilterChainFactoryTest.java
@@ -7,7 +7,6 @@
 package io.kroxylicious.proxy.bootstrap;
 
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 
@@ -15,9 +14,9 @@ import org.assertj.core.api.ListAssert;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import io.kroxylicious.proxy.config.Configuration;
 import io.kroxylicious.proxy.config.FilterDefinition;
 import io.kroxylicious.proxy.filter.FilterAndInvoker;
+import io.kroxylicious.proxy.filter.InvalidFilterConfigurationException;
 import io.kroxylicious.proxy.internal.filter.ExampleConfig;
 import io.kroxylicious.proxy.internal.filter.NettyFilterContext;
 import io.kroxylicious.proxy.internal.filter.OptionalConfigFilter;
@@ -25,8 +24,8 @@ import io.kroxylicious.proxy.internal.filter.TestFilter;
 import io.kroxylicious.proxy.internal.filter.TestFilterFactory;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class FilterChainFactoryTest {
@@ -43,17 +42,12 @@ class FilterChainFactoryTest {
     @Test
     void testNullFiltersInConfigResultsInEmptyList() {
         ScheduledExecutorService eventLoop = Executors.newScheduledThreadPool(1);
-        FilterChainFactory filterChainFactory = new FilterChainFactory(new Configuration(null, null, null, null, true));
+        FilterChainFactory filterChainFactory = new FilterChainFactory(null);
         List<FilterAndInvoker> filters = filterChainFactory.createFilters(new NettyFilterContext(eventLoop));
         assertNotNull(filters, "Filters list should not be null");
         assertTrue(filters.isEmpty(), "Filters list should be empty");
     }
 
-    @Test
-    void testConfigurationNotNullable() {
-        assertThatThrownBy(() -> new FilterChainFactory(null)).isInstanceOf(NullPointerException.class);
-    }
-
     @Test
     void testEmptyFiltersInConfigResultsInEmptyList() {
         assertFiltersCreated(List.of());
@@ -92,10 +86,10 @@ class FilterChainFactoryTest {
                 new FilterDefinition(TestFilter.class.getName(), null));
 
         // When
-        final Set<String> invalidFilters = FilterChainFactory.validateFilterConfiguration(filters);
+        var ex = assertThrows(InvalidFilterConfigurationException.class, () -> new FilterChainFactory(filters));
 
         // Then
-        assertThat(invalidFilters).containsOnly(TestFilter.class.getName());
+        assertThat(ex.getMessage()).contains(TestFilter.class.getName());
     }
 
     @Test
@@ -106,11 +100,10 @@ class FilterChainFactoryTest {
                 new FilterDefinition(OptionalConfigFilter.class.getName(), null));
 
         // When
-        final Set<String> invalidFilters = FilterChainFactory.validateFilterConfiguration(filters);
+        var ex = assertThrows(InvalidFilterConfigurationException.class, () -> new FilterChainFactory(filters));
 
         // Then
-        assertThat(invalidFilters).containsOnly(TestFilter.class.getName(),
-                TestFilter.class.getName());
+        assertThat(ex.getMessage()).contains(TestFilter.class.getName());
     }
 
     @Test
@@ -120,10 +113,10 @@ class FilterChainFactoryTest {
                 new FilterDefinition(TestFilter.class.getName(), config));
 
         // When
-        final Set<String> invalidFilters = FilterChainFactory.validateFilterConfiguration(filterDefinitions);
 
         // Then
-        assertThat(invalidFilters).isEmpty();
+        // no exception thrown;
+        assertThat(new FilterChainFactory(filterDefinitions)).isNotNull();
     }
 
     @Test
@@ -134,14 +127,13 @@ class FilterChainFactoryTest {
                 new FilterDefinition(OptionalConfigFilter.class.getName(), null));
 
         // When
-        final Set<String> invalidFilters = FilterChainFactory.validateFilterConfiguration(filterDefinitions);
 
         // Then
-        assertThat(invalidFilters).isEmpty();
+        assertThat(new FilterChainFactory(filterDefinitions)).isNotNull();
     }
 
     private ListAssert<FilterAndInvoker> assertFiltersCreated(List<FilterDefinition> filterDefinitions) {
-        FilterChainFactory filterChainFactory = new FilterChainFactory(new Configuration(null, null, filterDefinitions, null, true));
+        FilterChainFactory filterChainFactory = new FilterChainFactory(filterDefinitions);
         NettyFilterContext context = new NettyFilterContext(eventLoop);
         List<FilterAndInvoker> filters = filterChainFactory.createFilters(context);
         return assertThat(filters).isNotNull().hasSize(filterDefinitions.size());