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

gyfora pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-kubernetes-operator.git

commit 30928cbc29348ed01d7461668a0deb48c12adc35
Author: Dennis-Mircea Ciupitu <[email protected]>
AuthorDate: Wed Jun 17 15:27:02 2026 +0300

    [FLINK-39511][autoscaler] FLIP-514: Custom Evaluator plugin for Flink 
Autoscaler
---
 docs/content.zh/docs/operations/plugins.md         |  92 +++++++++++-
 docs/content/docs/operations/plugins.md            |  92 ++++++++++++
 .../generated/auto_scaler_configuration.html       |  18 +++
 .../standalone/StandaloneAutoscalerEntrypoint.java |  11 +-
 .../standalone/utils/AutoscalerUtils.java          |  55 +++++++
 .../standalone/utils/AutoscalerUtilsTest.java      |  43 ++++++
 ...utoscaler.metrics.ScalingMetricsEvaluatorPlugin |   3 +-
 .../apache/flink/autoscaler/JobAutoScalerImpl.java |  34 +----
 .../flink/autoscaler/ScalingMetricEvaluator.java   | 129 ++++++++++++-----
 .../flink/autoscaler/config/AutoScalerOptions.java | 120 +++++++++++++--
 .../metrics/FlinkAutoscalerEvaluator.java          | 118 ---------------
 .../metrics/ScalingMetricsEvaluatorPlugin.java     | 114 +++++++++++++++
 .../autoscaler/AutoScalerCustomEvaluatorTest.java  | 118 +++++++++++++--
 .../flink/autoscaler/BacklogBasedScalingTest.java  |   7 +-
 .../autoscaler/DelayedScaleDownEndToEndTest.java   |   5 +-
 .../flink/autoscaler/JobAutoScalerImplTest.java    | 159 ++------------------
 .../MetricsCollectionAndEvaluationTest.java        |  11 +-
 .../autoscaler/RecommendedParallelismTest.java     |   7 +-
 .../autoscaler/ScalingMetricEvaluatorTest.java     | 161 ++++++++++++++++++---
 .../autoscaler/metrics/TestCustomEvaluator.java    |  10 +-
 .../flink/kubernetes/operator/FlinkOperator.java   |   5 +-
 .../operator/autoscaler/AutoscalerFactory.java     |  12 +-
 .../operator/autoscaler/AutoscalerUtils.java       |  75 ----------
 .../kubernetes/operator/utils/AutoscalerUtils.java |  61 ++++++++
 .../operator/autoscaler/AutoscalerFactoryTest.java |   3 +-
 .../operator/autoscaler/AutoscalerUtilsTest.java   |  22 ++-
 .../TestingFlinkDeploymentController.java          |  20 +--
 ...toscaler.metrics.ScalingMetricsEvaluatorPlugin} |   0
 28 files changed, 993 insertions(+), 512 deletions(-)

diff --git a/docs/content.zh/docs/operations/plugins.md 
b/docs/content.zh/docs/operations/plugins.md
index f420358a8..b5ae5e1a2 100644
--- a/docs/content.zh/docs/operations/plugins.md
+++ b/docs/content.zh/docs/operations/plugins.md
@@ -186,4 +186,94 @@ The following steps demonstrate how to develop and use a 
custom mutator.
     2023-12-12 06:26:56,667 o.a.f.k.o.u.MutatorUtils [INFO ] Discovered 
mutator from plugin directory[/opt/flink/plugins]: 
org.apache.flink.mutator.CustomFlinkMutator.
     ```
 
-## Flink Autoscaler Custom Evaluator
+## Custom Scaling Metric Evaluators
+
+`ScalingMetricsEvaluatorPlugin` is a pluggable component that allows users to 
provide custom scaling-metric evaluation logic on top of the metrics evaluated 
internally by the autoscaler. Custom metric evaluators are discovered through 
the 
[Plugins](https://nightlies.apache.org/flink/flink-docs-master/docs/deployment/filesystems/plugins)
 mechanism when running inside the Kubernetes operator, and through the 
standard Java `ServiceLoader` mechanism when running with 
`flink-autoscaler-standa [...]
+
+For each evaluation cycle, the autoscaler invokes the custom metric evaluator 
selected via the `job.autoscaler.metrics.custom-evaluators` configuration 
option once per job vertex. The metrics returned by the custom evaluator are 
merged on top of the internally evaluated metrics, allowing users to override 
or augment specific `ScalingMetric` values (e.g. `TARGET_DATA_RATE`, 
`TRUE_PROCESSING_RATE`, `CATCH_UP_DATA_RATE`).
+
+All `job.autoscaler.*` keys related to custom metric evaluators also support 
the legacy `kubernetes.operator.`-prefixed form as a fallback (for example, 
`kubernetes.operator.job.autoscaler.metrics.custom-evaluators`). The canonical 
key takes precedence on overlap.
+
+The following steps demonstrate how to develop and use a custom metric 
evaluator.
+
+1. Implement the `ScalingMetricsEvaluatorPlugin` interface:
+    ```java
+    package org.apache.flink.autoscaler.custom;
+
+    import org.apache.flink.autoscaler.metrics.EvaluatedScalingMetric;
+    import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
+    import org.apache.flink.autoscaler.metrics.ScalingMetric;
+    import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+    import java.util.HashMap;
+    import java.util.Map;
+
+    /** Custom metric evaluator implementation of {@link 
ScalingMetricsEvaluatorPlugin}. */
+    public class CustomEvaluator implements ScalingMetricsEvaluatorPlugin {
+        
+        @Override
+        public Map<ScalingMetric, EvaluatedScalingMetric> 
evaluateVertexMetrics(
+                JobVertexID vertex,
+                Map<ScalingMetric, EvaluatedScalingMetric> evaluatedMetrics,
+                Context context) {
+            Map<ScalingMetric, EvaluatedScalingMetric> overrides = new 
HashMap<>();
+            // Example: override the target data rate for source vertices based
+            // on an evaluator-specific option read from the merged 
configuration.
+            double target = context.getConfig().getDouble("target-data-rate", 
0.0);
+            if (target > 0 && context.getTopology().isSource(vertex)) {
+                overrides.put(
+                        ScalingMetric.TARGET_DATA_RATE,
+                        EvaluatedScalingMetric.avg(target));
+            }
+            return overrides;
+        }
+    }
+    ```
+
+   The `Context` object exposes an un-modifiable view of the configuration, 
the metrics history, previously evaluated vertex metrics (evaluation happens 
topologically), the job topology, backlog status, and max restart time. The 
configuration is the job configuration with the evaluator-specific options 
(prefix stripped) merged on top, so an evaluator-specific key overrides the 
job-level value of the same key.
+
+2. Create the service definition file 
`org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin` in 
`META-INF/services` with the fully-qualified class name of your implementation:
+    ```text
+    org.apache.flink.autoscaler.custom.CustomEvaluator
+    ```
+
+3. Use the Maven tool to package the project and generate the custom metric 
evaluator JAR.
+
+4. Select the custom metric evaluator via configuration. The evaluator whose 
implementation class FQN matches the configured 
`job.autoscaler.metrics.custom-evaluator.<name>.class` value will be invoked, 
and any other `job.autoscaler.metrics.custom-evaluator.<name>.*` entries are 
merged on top of the job configuration with the 
`job.autoscaler.metrics.custom-evaluator.<instance>.` prefix stripped, and 
exposed via `Context.getConfig()`, taking precedence over the job-level value 
of the same [...]
+    ```yaml
+    job.autoscaler.metrics.custom-evaluators: my-evaluator
+    job.autoscaler.metrics.custom-evaluator.my-evaluator.class: 
org.apache.flink.autoscaler.custom.CustomEvaluator
+    job.autoscaler.metrics.custom-evaluator.my-evaluator.target-data-rate: 
100000.0
+    ```
+   {{< hint warning >}}
+   **Only one custom metric evaluator per pipeline is supported for now**. 
Currently, `job.autoscaler.metrics.custom-evaluators` is parsed as a list, but 
if more than one entry is configured the autoscaler logs a warning and falls 
back to the first entry, ignoring the rest. Registering multiple 
implementations via `META-INF/services` is fine as they form a registry that 
different jobs can select from by class FQN, but a single job cannot chain or 
compose more than one evaluator. Multi-in [...]
+   {{< /hint >}}
+
+5. Deploy the evaluator.
+
+   - **Operator deployment** - create a Dockerfile to build a custom image 
from the `apache/flink-kubernetes-operator` official image and copy the 
generated JAR to a custom metric evaluator plugin directory under 
`/opt/flink/plugins` (the value of the `FLINK_PLUGINS_DIR` environment variable 
in the flink-kubernetes-operator helm chart). The structure of the custom 
evaluator directory under `/opt/flink/plugins` is as follows:
+       ```text
+       /opt/flink/plugins
+           ├── custom-evaluator
+           │   ├── custom-evaluator.jar
+           └── ...
+       ```
+
+     With the custom metric evaluator directory location, the Dockerfile is 
defined as follows:
+       ```shell script
+       FROM apache/flink-kubernetes-operator
+       ENV FLINK_PLUGINS_DIR=/opt/flink/plugins
+       ENV CUSTOM_EVALUATOR_DIR=custom-evaluator
+       RUN mkdir $FLINK_PLUGINS_DIR/$CUSTOM_EVALUATOR_DIR
+       COPY custom-evaluator.jar $FLINK_PLUGINS_DIR/$CUSTOM_EVALUATOR_DIR/
+       ```
+
+     Install the flink-kubernetes-operator helm chart with the custom image 
and verify the `deploy/flink-kubernetes-operator` log has:
+       ```text
+       o.a.f.k.o.a.AutoscalerUtils [INFO ] Discovered custom metric evaluator 
from plugin directory[/opt/flink/plugins]: 
org.apache.flink.autoscaler.custom.CustomEvaluator.
+       ```
+
+   - **Standalone autoscaler** - simply place the custom metric evaluator JAR 
on the classpath of the `flink-autoscaler-standalone` process. It will be 
picked up automatically via Java's `ServiceLoader` and discovery will be logged:
+       ```text
+       o.a.f.a.s.AutoscalerUtils [INFO ] Discovered custom metric evaluator 
via ServiceLoader: org.apache.flink.autoscaler.custom.CustomEvaluator.
+       ```
diff --git a/docs/content/docs/operations/plugins.md 
b/docs/content/docs/operations/plugins.md
index 4f387add6..21ef284d4 100644
--- a/docs/content/docs/operations/plugins.md
+++ b/docs/content/docs/operations/plugins.md
@@ -185,3 +185,95 @@ The following steps demonstrate how to develop and use a 
custom mutator.
     ```text
     2023-12-12 06:26:56,667 o.a.f.k.o.u.MutatorUtils [INFO ] Discovered 
mutator from plugin directory[/opt/flink/plugins]: 
org.apache.flink.mutator.CustomFlinkMutator.
     ```
+
+## Custom Scaling Metric Evaluators
+
+`ScalingMetricsEvaluatorPlugin` is a pluggable component that allows users to 
provide custom scaling-metric evaluation logic on top of the metrics evaluated 
internally by the autoscaler. Custom metric evaluators are discovered through 
the 
[Plugins](https://nightlies.apache.org/flink/flink-docs-master/docs/deployment/filesystems/plugins)
 mechanism when running inside the Kubernetes operator, and through the 
standard Java `ServiceLoader` mechanism when running with 
`flink-autoscaler-standa [...]
+
+For each evaluation cycle, the autoscaler invokes the custom metric evaluator 
selected via the `job.autoscaler.metrics.custom-evaluators` configuration 
option once per job vertex. The metrics returned by the custom evaluator are 
merged on top of the internally evaluated metrics, allowing users to override 
or augment specific `ScalingMetric` values (e.g. `TARGET_DATA_RATE`, 
`TRUE_PROCESSING_RATE`, `CATCH_UP_DATA_RATE`).
+
+All `job.autoscaler.*` keys related to custom metric evaluators also support 
the legacy `kubernetes.operator.`-prefixed form as a fallback (for example, 
`kubernetes.operator.job.autoscaler.metrics.custom-evaluators`). The canonical 
key takes precedence on overlap.
+
+The following steps demonstrate how to develop and use a custom metric 
evaluator.
+
+1. Implement the `ScalingMetricsEvaluatorPlugin` interface:
+    ```java
+    package org.apache.flink.autoscaler.custom;
+
+    import org.apache.flink.autoscaler.metrics.EvaluatedScalingMetric;
+    import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
+    import org.apache.flink.autoscaler.metrics.ScalingMetric;
+    import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+    import java.util.HashMap;
+    import java.util.Map;
+
+    /** Custom metric evaluator implementation of {@link 
ScalingMetricsEvaluatorPlugin}. */
+    public class CustomEvaluator implements ScalingMetricsEvaluatorPlugin {
+
+        @Override
+        public Map<ScalingMetric, EvaluatedScalingMetric> 
evaluateVertexMetrics(
+                JobVertexID vertex,
+                Map<ScalingMetric, EvaluatedScalingMetric> evaluatedMetrics,
+                Context context) {
+            Map<ScalingMetric, EvaluatedScalingMetric> overrides = new 
HashMap<>();
+            // Example: override the target data rate for source vertices based
+            // on an evaluator-specific option read from the merged 
configuration.
+            double target = context.getConfig().getDouble("target-data-rate", 
0.0);
+            if (target > 0 && context.getTopology().isSource(vertex)) {
+                overrides.put(
+                        ScalingMetric.TARGET_DATA_RATE,
+                        EvaluatedScalingMetric.avg(target));
+            }
+            return overrides;
+        }
+    }
+    ```
+
+   The `Context` object exposes an un-modifiable view of the configuration, 
the metrics history, previously evaluated vertex metrics (evaluation happens 
topologically), the job topology, backlog status, and max restart time. The 
configuration is the job configuration with the evaluator-specific options 
(prefix stripped) merged on top, so an evaluator-specific key overrides the 
job-level value of the same key.
+
+2. Create the service definition file 
`org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin` in 
`META-INF/services` with the fully-qualified class name of your implementation:
+    ```text
+    org.apache.flink.autoscaler.custom.CustomEvaluator
+    ```
+
+3. Use the Maven tool to package the project and generate the custom metric 
evaluator JAR.
+
+4. Select the custom metric evaluator via configuration. The evaluator whose 
implementation class FQN matches the configured 
`job.autoscaler.metrics.custom-evaluator.<name>.class` value will be invoked, 
and any other `job.autoscaler.metrics.custom-evaluator.<name>.*` entries are 
merged on top of the job configuration with the 
`job.autoscaler.metrics.custom-evaluator.<instance>.` prefix stripped, and 
exposed via `Context.getConfig()`, taking precedence over the job-level value 
of the same [...]
+    ```yaml
+    job.autoscaler.metrics.custom-evaluators: my-evaluator
+    job.autoscaler.metrics.custom-evaluator.my-evaluator.class: 
org.apache.flink.autoscaler.custom.CustomEvaluator
+    job.autoscaler.metrics.custom-evaluator.my-evaluator.target-data-rate: 
100000.0
+    ```
+   {{< hint warning >}}
+   **Only one custom metric evaluator per pipeline is supported for now**. 
Currently, `job.autoscaler.metrics.custom-evaluators` is parsed as a list, but 
if more than one entry is configured the autoscaler logs a warning and falls 
back to the first entry, ignoring the rest. Registering multiple 
implementations via `META-INF/services` is fine as they form a registry that 
different jobs can select from by class FQN, but a single job cannot chain or 
compose more than one evaluator. Multi-in [...]
+   {{< /hint >}}
+
+5. Deploy the evaluator.
+
+    - **Operator deployment** - create a Dockerfile to build a custom image 
from the `apache/flink-kubernetes-operator` official image and copy the 
generated JAR to a custom metric evaluator plugin directory under 
`/opt/flink/plugins` (the value of the `FLINK_PLUGINS_DIR` environment variable 
in the flink-kubernetes-operator helm chart). The structure of the custom 
evaluator directory under `/opt/flink/plugins` is as follows:
+        ```text
+        /opt/flink/plugins
+            ├── custom-evaluator
+            │   ├── custom-evaluator.jar
+            └── ...
+        ```
+
+        With the custom metric evaluator directory location, the Dockerfile is 
defined as follows:
+        ```shell script
+        FROM apache/flink-kubernetes-operator
+        ENV FLINK_PLUGINS_DIR=/opt/flink/plugins
+        ENV CUSTOM_EVALUATOR_DIR=custom-evaluator
+        RUN mkdir $FLINK_PLUGINS_DIR/$CUSTOM_EVALUATOR_DIR
+        COPY custom-evaluator.jar $FLINK_PLUGINS_DIR/$CUSTOM_EVALUATOR_DIR/
+        ```
+
+        Install the flink-kubernetes-operator helm chart with the custom image 
and verify the `deploy/flink-kubernetes-operator` log has:
+        ```text
+        o.a.f.k.o.a.AutoscalerUtils [INFO ] Discovered custom metric evaluator 
from plugin directory[/opt/flink/plugins]: 
org.apache.flink.autoscaler.custom.CustomEvaluator.
+        ```
+
+    - **Standalone autoscaler** - simply place the custom metric evaluator JAR 
on the classpath of the `flink-autoscaler-standalone` process. It will be 
picked up automatically via Java's `ServiceLoader` and discovery will be logged:
+        ```text
+        o.a.f.a.s.AutoscalerUtils [INFO ] Discovered custom metric evaluator 
via ServiceLoader: org.apache.flink.autoscaler.custom.CustomEvaluator.
+        ```
diff --git a/docs/layouts/shortcodes/generated/auto_scaler_configuration.html 
b/docs/layouts/shortcodes/generated/auto_scaler_configuration.html
index 3c12ee108..2844f5a2f 100644
--- a/docs/layouts/shortcodes/generated/auto_scaler_configuration.html
+++ b/docs/layouts/shortcodes/generated/auto_scaler_configuration.html
@@ -92,6 +92,24 @@
             <td><p>Enum</p></td>
             <td>Metric aggregator to use for busyTime metrics. This affects 
how true processing/output rate will be computed. Using max allows us to handle 
jobs with data skew more robustly, while avg may provide better stability when 
we know that the load distribution is even.<br /><br />Possible 
values:<ul><li>"AVG"</li><li>"MAX"</li><li>"MIN"</li></ul></td>
         </tr>
+        <tr>
+            
<td><h5>job.autoscaler.metrics.custom-evaluator.&lt;name&gt;.&lt;parameter&gt;</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>String</td>
+            <td>Free-form, evaluator-specific options for the custom metric 
evaluator instance &lt;name&gt;. All keys with this prefix are passed to the 
evaluator at runtime with the 
'job.autoscaler.metrics.custom-evaluator.&lt;name&gt;.' prefix stripped.</td>
+        </tr>
+        <tr>
+            
<td><h5>job.autoscaler.metrics.custom-evaluator.&lt;name&gt;.class</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>String</td>
+            <td>Fully-qualified class name of the implementation to 
instantiate for the custom metric evaluator instance &lt;name&gt; declared in 
'job.autoscaler.metrics.custom-evaluators'. The class must be discovered via 
the Plugin / ServiceLoader mechanism and registered under 
META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin.</td>
+        </tr>
+        <tr>
+            <td><h5>job.autoscaler.metrics.custom-evaluators</h5></td>
+            <td style="word-wrap: break-word;"></td>
+            <td>List&lt;String&gt;</td>
+            <td>List of named custom metric evaluator instances to register 
with the autoscaler. For each &lt;name&gt; entry, the implementation class to 
instantiate must be set via 
'job.autoscaler.metrics.custom-evaluator.&lt;name&gt;.class', and additional 
per-instance options live under 
'job.autoscaler.metrics.custom-evaluator.&lt;name&gt;.&lt;option&gt;'. Only a 
single instance is honored for now: if more than one is configured, the 
autoscaler logs a warning and uses the first entry, [...]
+        </tr>
         <tr>
             <td><h5>job.autoscaler.metrics.window</h5></td>
             <td style="word-wrap: break-word;">15 min</td>
diff --git 
a/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/StandaloneAutoscalerEntrypoint.java
 
b/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/StandaloneAutoscalerEntrypoint.java
index 957af4c41..443c296e4 100644
--- 
a/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/StandaloneAutoscalerEntrypoint.java
+++ 
b/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/StandaloneAutoscalerEntrypoint.java
@@ -27,8 +27,10 @@ import org.apache.flink.autoscaler.RestApiMetricsCollector;
 import org.apache.flink.autoscaler.ScalingExecutor;
 import org.apache.flink.autoscaler.ScalingMetricEvaluator;
 import org.apache.flink.autoscaler.event.AutoScalerEventHandler;
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
 import 
org.apache.flink.autoscaler.standalone.flinkcluster.FlinkClusterJobListFetcher;
 import 
org.apache.flink.autoscaler.standalone.realizer.RescaleApiScalingRealizer;
+import org.apache.flink.autoscaler.standalone.utils.AutoscalerUtils;
 import org.apache.flink.autoscaler.state.AutoScalerStateStore;
 import org.apache.flink.client.program.rest.RestClusterClient;
 import org.apache.flink.configuration.Configuration;
@@ -38,6 +40,8 @@ import 
org.apache.flink.runtime.highavailability.nonha.standalone.StandaloneClie
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collection;
+
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.FLINK_CLIENT_TIMEOUT;
 import static 
org.apache.flink.autoscaler.standalone.config.AutoscalerStandaloneOptions.FETCHER_FLINK_CLUSTER_HOST;
 import static 
org.apache.flink.autoscaler.standalone.config.AutoscalerStandaloneOptions.FETCHER_FLINK_CLUSTER_PORT;
@@ -88,14 +92,15 @@ public class StandaloneAutoscalerEntrypoint {
             JobAutoScaler<KEY, Context> createJobAutoscaler(
                     AutoScalerEventHandler<KEY, Context> eventHandler,
                     AutoScalerStateStore<KEY, Context> stateStore) {
+        Collection<ScalingMetricsEvaluatorPlugin> customEvaluators =
+                AutoscalerUtils.discoverCustomEvaluators();
         return new JobAutoScalerImpl<>(
                 new RestApiMetricsCollector<>(),
-                new ScalingMetricEvaluator(),
+                new ScalingMetricEvaluator(customEvaluators),
                 new ScalingExecutor<>(eventHandler, stateStore),
                 eventHandler,
                 new RescaleApiScalingRealizer<>(eventHandler),
-                stateStore,
-                null);
+                stateStore);
     }
 
     @VisibleForTesting
diff --git 
a/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/utils/AutoscalerUtils.java
 
b/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/utils/AutoscalerUtils.java
new file mode 100644
index 000000000..39f4f0539
--- /dev/null
+++ 
b/flink-autoscaler-standalone/src/main/java/org/apache/flink/autoscaler/standalone/utils/AutoscalerUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.flink.autoscaler.standalone.utils;
+
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ServiceLoader;
+
+/** Autoscaler related utility methods for the standalone autoscaler. */
+public class AutoscalerUtils {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AutoscalerUtils.class);
+
+    /**
+     * Discovers custom metric evaluators for the standalone autoscaler via 
Java's {@link
+     * ServiceLoader} mechanism. Implementations must be registered under 
{@code
+     * 
META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin}
 on the
+     * classpath.
+     *
+     * @return The list of discovered custom metric evaluators.
+     */
+    public static Collection<ScalingMetricsEvaluatorPlugin> 
discoverCustomEvaluators() {
+        List<ScalingMetricsEvaluatorPlugin> customEvaluators = new 
ArrayList<>();
+        ServiceLoader.load(ScalingMetricsEvaluatorPlugin.class)
+                .forEach(
+                        customEvaluator -> {
+                            LOG.info(
+                                    "Discovered custom metric evaluator via 
ServiceLoader: {}.",
+                                    customEvaluator.getClass().getName());
+                            customEvaluators.add(customEvaluator);
+                        });
+        return customEvaluators;
+    }
+}
diff --git 
a/flink-autoscaler-standalone/src/test/java/org/apache/flink/autoscaler/standalone/utils/AutoscalerUtilsTest.java
 
b/flink-autoscaler-standalone/src/test/java/org/apache/flink/autoscaler/standalone/utils/AutoscalerUtilsTest.java
new file mode 100644
index 000000000..d720dbdac
--- /dev/null
+++ 
b/flink-autoscaler-standalone/src/test/java/org/apache/flink/autoscaler/standalone/utils/AutoscalerUtilsTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.flink.autoscaler.standalone.utils;
+
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
+import org.apache.flink.autoscaler.metrics.TestCustomEvaluator;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Unit tests for {@link AutoscalerUtils}. */
+class AutoscalerUtilsTest {
+
+    @Test
+    void testDiscoverCustomEvaluators() {
+        Collection<ScalingMetricsEvaluatorPlugin> evaluators =
+                AutoscalerUtils.discoverCustomEvaluators();
+
+        assertThat(evaluators)
+                .as(
+                        "Expected to discover the TestCustomEvaluator 
registered under META-INF/services")
+                .hasSize(1)
+                .hasOnlyElementsOfType(TestCustomEvaluator.class);
+    }
+}
diff --git 
a/flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator
 
b/flink-autoscaler-standalone/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin
similarity index 93%
copy from 
flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator
copy to 
flink-autoscaler-standalone/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin
index a150d9783..a0deb60ed 100644
--- 
a/flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator
+++ 
b/flink-autoscaler-standalone/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin
@@ -16,4 +16,5 @@
 # limitations under the License.
 #
 
-org.apache.flink.autoscaler.metrics.TestCustomEvaluator
\ No newline at end of file
+org.apache.flink.autoscaler.metrics.TestCustomEvaluator
+
diff --git 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/JobAutoScalerImpl.java
 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/JobAutoScalerImpl.java
index ddfd78cf3..b99f4da36 100644
--- 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/JobAutoScalerImpl.java
+++ 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/JobAutoScalerImpl.java
@@ -19,17 +19,14 @@ package org.apache.flink.autoscaler;
 
 import org.apache.flink.annotation.VisibleForTesting;
 import org.apache.flink.api.common.JobStatus;
-import org.apache.flink.api.java.tuple.Tuple2;
 import org.apache.flink.autoscaler.config.AutoScalerOptions;
 import org.apache.flink.autoscaler.event.AutoScalerEventHandler;
 import org.apache.flink.autoscaler.exceptions.NotReadyException;
 import org.apache.flink.autoscaler.metrics.AutoscalerFlinkMetrics;
 import org.apache.flink.autoscaler.metrics.EvaluatedMetrics;
-import org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator;
 import org.apache.flink.autoscaler.realizer.ScalingRealizer;
 import org.apache.flink.autoscaler.state.AutoScalerStateStore;
 import org.apache.flink.autoscaler.tuning.ConfigChanges;
-import org.apache.flink.configuration.Configuration;
 import org.apache.flink.configuration.PipelineOptions;
 import org.apache.flink.util.Preconditions;
 
@@ -39,11 +36,9 @@ import org.slf4j.LoggerFactory;
 import java.time.Clock;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.AUTOSCALER_ENABLED;
-import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.CUSTOM_EVALUATOR_NAME;
 import static 
org.apache.flink.autoscaler.metrics.AutoscalerFlinkMetrics.initRecommendedParallelism;
 import static 
org.apache.flink.autoscaler.metrics.AutoscalerFlinkMetrics.resetRecommendedParallelism;
 import static 
org.apache.flink.autoscaler.metrics.ScalingHistoryUtils.getTrimmedScalingHistory;
@@ -63,7 +58,6 @@ public class JobAutoScalerImpl<KEY, Context extends 
JobAutoScalerContext<KEY>>
     private final AutoScalerEventHandler<KEY, Context> eventHandler;
     private final ScalingRealizer<KEY, Context> scalingRealizer;
     private final AutoScalerStateStore<KEY, Context> stateStore;
-    private final Map<String, FlinkAutoscalerEvaluator> customEvaluators;
 
     private Clock clock = Clock.systemDefaultZone();
 
@@ -79,15 +73,13 @@ public class JobAutoScalerImpl<KEY, Context extends 
JobAutoScalerContext<KEY>>
             ScalingExecutor<KEY, Context> scalingExecutor,
             AutoScalerEventHandler<KEY, Context> eventHandler,
             ScalingRealizer<KEY, Context> scalingRealizer,
-            AutoScalerStateStore<KEY, Context> stateStore,
-            Map<String, FlinkAutoscalerEvaluator> customEvaluators) {
+            AutoScalerStateStore<KEY, Context> stateStore) {
         this.metricsCollector = metricsCollector;
         this.evaluator = evaluator;
         this.scalingExecutor = scalingExecutor;
         this.eventHandler = eventHandler;
         this.scalingRealizer = scalingRealizer;
         this.stateStore = stateStore;
-        this.customEvaluators = customEvaluators;
     }
 
     @Override
@@ -212,14 +204,8 @@ public class JobAutoScalerImpl<KEY, Context extends 
JobAutoScalerContext<KEY>>
         // Scaling tracking data contains previous restart times that are 
taken into account
         var restartTime = 
scalingTracking.getMaxRestartTimeOrDefault(ctx.getConfiguration());
 
-        var customEvaluatorWithConfig = 
getCustomEvaluatorIfRequired(ctx.getConfiguration());
-
         var evaluatedMetrics =
-                evaluator.evaluate(
-                        ctx.getConfiguration(),
-                        collectedMetrics,
-                        restartTime,
-                        customEvaluatorWithConfig);
+                evaluator.evaluate(ctx.getConfiguration(), collectedMetrics, 
restartTime);
         LOG.debug("Evaluated metrics: {}", evaluatedMetrics);
         lastEvaluatedMetrics.put(ctx.getJobKey(), evaluatedMetrics);
 
@@ -274,20 +260,4 @@ public class JobAutoScalerImpl<KEY, Context extends 
JobAutoScalerContext<KEY>>
         this.metricsCollector.setClock(clock);
         this.scalingExecutor.setClock(clock);
     }
-
-    @VisibleForTesting
-    protected Tuple2<FlinkAutoscalerEvaluator, Configuration> 
getCustomEvaluatorIfRequired(
-            Configuration conf) {
-        return Optional.ofNullable(conf.get(CUSTOM_EVALUATOR_NAME))
-                .map(
-                        name -> {
-                            FlinkAutoscalerEvaluator evaluator = 
customEvaluators.get(name);
-                            return evaluator != null
-                                    ? new Tuple2<>(
-                                            evaluator,
-                                            
AutoScalerOptions.forCustomEvaluator(conf, name))
-                                    : null;
-                        })
-                .orElse(null);
-    }
 }
diff --git 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/ScalingMetricEvaluator.java
 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/ScalingMetricEvaluator.java
index 9fb59ff6a..4a1083742 100644
--- 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/ScalingMetricEvaluator.java
+++ 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/ScalingMetricEvaluator.java
@@ -24,9 +24,9 @@ import 
org.apache.flink.autoscaler.metrics.CollectedMetricHistory;
 import org.apache.flink.autoscaler.metrics.CollectedMetrics;
 import org.apache.flink.autoscaler.metrics.EvaluatedMetrics;
 import org.apache.flink.autoscaler.metrics.EvaluatedScalingMetric;
-import org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator;
 import org.apache.flink.autoscaler.metrics.MetricAggregator;
 import org.apache.flink.autoscaler.metrics.ScalingMetric;
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
 import org.apache.flink.autoscaler.topology.JobTopology;
 import org.apache.flink.autoscaler.utils.AutoScalerUtils;
 import org.apache.flink.configuration.Configuration;
@@ -41,13 +41,17 @@ import javax.annotation.Nullable;
 
 import java.time.Duration;
 import java.time.Instant;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.SortedMap;
+import java.util.stream.Collectors;
 
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.BACKLOG_PROCESSING_LAG_THRESHOLD;
+import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.CUSTOM_EVALUATORS;
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.TARGET_UTILIZATION_BOUNDARY;
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.UTILIZATION_MAX;
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.UTILIZATION_MIN;
@@ -75,13 +79,14 @@ public class ScalingMetricEvaluator {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ScalingMetricEvaluator.class);
 
+    private final Collection<ScalingMetricsEvaluatorPlugin> customEvaluators;
+
+    public ScalingMetricEvaluator(Collection<ScalingMetricsEvaluatorPlugin> 
customEvaluators) {
+        this.customEvaluators = customEvaluators;
+    }
+
     public EvaluatedMetrics evaluate(
-            Configuration conf,
-            CollectedMetricHistory collectedMetrics,
-            Duration restartTime,
-            @Nullable
-                    Tuple2<FlinkAutoscalerEvaluator, Configuration>
-                            customEvaluatorWithConfig) {
+            Configuration conf, CollectedMetricHistory collectedMetrics, 
Duration restartTime) {
         LOG.debug("Restart time used in metrics evaluation: {}", restartTime);
         var scalingOutput = new HashMap<JobVertexID, Map<ScalingMetric, 
EvaluatedScalingMetric>>();
         var metricsHistory = collectedMetrics.getMetricHistory();
@@ -89,21 +94,21 @@ public class ScalingMetricEvaluator {
 
         boolean processingBacklog = isProcessingBacklog(topology, 
metricsHistory, conf);
 
+        var customEvaluatorWithConfig = getCustomEvaluatorIfRequired(conf);
         var customEvaluationSession =
                 Optional.ofNullable(customEvaluatorWithConfig)
                         .map(
                                 info ->
                                         Tuple2.of(
                                                 info.f0,
-                                                new 
FlinkAutoscalerEvaluator.Context(
-                                                        new 
UnmodifiableConfiguration(conf),
+                                                new 
ScalingMetricsEvaluatorPlugin.Context(
+                                                        
mergeEvaluatorConfig(conf, info.f1),
                                                         
Collections.unmodifiableSortedMap(
                                                                 
metricsHistory),
                                                         
Collections.unmodifiableMap(scalingOutput),
                                                         topology,
                                                         processingBacklog,
-                                                        restartTime,
-                                                        info.f1)))
+                                                        restartTime)))
                         .orElse(null);
 
         for (var vertex : topology.getVerticesInTopologicalOrder()) {
@@ -161,7 +166,7 @@ public class ScalingMetricEvaluator {
             boolean processingBacklog,
             Duration restartTime,
             @Nullable
-                    Tuple2<FlinkAutoscalerEvaluator, 
FlinkAutoscalerEvaluator.Context>
+                    Tuple2<ScalingMetricsEvaluatorPlugin, 
ScalingMetricsEvaluatorPlugin.Context>
                             customEvaluationSession) {
 
         var latestVertexMetrics =
@@ -679,12 +684,12 @@ public class ScalingMetricEvaluator {
     }
 
     /**
-     * Executes the provided custom evaluator for the given job vertex. Calls 
{@link
-     * FlinkAutoscalerEvaluator#evaluateVertexMetrics} to evaluate scaling 
metrics.
+     * Executes the provided custom metric evaluator for the given job vertex. 
Calls {@link
+     * ScalingMetricsEvaluatorPlugin#evaluateVertexMetrics} to evaluate 
scaling metrics.
      *
      * @param vertex The job vertex being evaluated.
      * @param evaluatedMetrics Current evaluated metrics.
-     * @param customEvaluationSession A tuple containing the custom evaluator 
and evaluation
+     * @param customEvaluationSession A tuple containing the custom metric 
evaluator and evaluation
      *     context.
      * @return A map of scaling metrics, with its corresponding evaluated 
scaling metric.
      */
@@ -692,19 +697,14 @@ public class ScalingMetricEvaluator {
     protected static Map<ScalingMetric, EvaluatedScalingMetric> 
runCustomEvaluator(
             JobVertexID vertex,
             Map<ScalingMetric, EvaluatedScalingMetric> evaluatedMetrics,
-            Tuple2<FlinkAutoscalerEvaluator, FlinkAutoscalerEvaluator.Context>
+            Tuple2<ScalingMetricsEvaluatorPlugin, 
ScalingMetricsEvaluatorPlugin.Context>
                     customEvaluationSession) {
         try {
             return customEvaluationSession.f0.evaluateVertexMetrics(
                     vertex, evaluatedMetrics, customEvaluationSession.f1);
-        } catch (UnsupportedOperationException e) {
-            LOG.warn(
-                    "Custom evaluator {} tried accessing an un-modifiable 
view.",
-                    customEvaluationSession.f0.getClass(),
-                    e);
         } catch (Exception e) {
             LOG.warn(
-                    "Custom evaluator {} threw an exception.",
+                    "Custom metric evaluator {} threw an exception.",
                     customEvaluationSession.f0.getClass(),
                     e);
         }
@@ -713,12 +713,82 @@ public class ScalingMetricEvaluator {
     }
 
     /**
-     * Merges the incoming evaluated metrics into actual evaluated metrics.
+     * Builds the configuration handed to a custom evaluator: the full job 
configuration with the
+     * evaluator-specific options (prefix already stripped) merged on top, so 
an evaluator-specific
+     * key takes precedence over the job-level value of the same key. The 
result is an un-modifiable
+     * view.
+     *
+     * @param jobConf The full job configuration.
+     * @param evaluatorConf The evaluator-specific options with their prefix 
already stripped.
+     * @return An un-modifiable merged configuration.
+     */
+    @VisibleForTesting
+    protected static Configuration mergeEvaluatorConfig(
+            Configuration jobConf, Configuration evaluatorConf) {
+        Configuration merged = new Configuration(jobConf);
+        merged.addAll(evaluatorConf);
+        return new UnmodifiableConfiguration(merged);
+    }
+
+    /**
+     * Resolves the configured custom metric evaluator (if any) from the 
registered evaluators.
      *
-     * @param actual The target evaluated metrics map to merge into.
-     * @param incoming The incoming map containing new evaluated metrics map 
to be merged
-     *     (nullable).
+     * @param conf The job configuration.
+     * @return A tuple of the resolved {@link ScalingMetricsEvaluatorPlugin} 
and its
+     *     evaluator-specific configuration, or {@code null} if no custom 
evaluator is configured or
+     *     resolvable.
      */
+    @VisibleForTesting
+    protected Tuple2<ScalingMetricsEvaluatorPlugin, Configuration> 
getCustomEvaluatorIfRequired(
+            Configuration conf) {
+        List<String> instances = conf.get(CUSTOM_EVALUATORS);
+        if (instances == null || instances.isEmpty()) {
+            return null;
+        }
+        if (instances.size() > 1) {
+            LOG.warn(
+                    "Only a single custom metric evaluator is currently 
supported, but {} were configured via '{}': {}. "
+                            + "Falling back to the first entry ('{}'); the 
remaining entries will be ignored. "
+                            + "Multi-instance support (with a 
priority/ordering contract) will be added as a follow-up.",
+                    instances.size(),
+                    CUSTOM_EVALUATORS.key(),
+                    instances,
+                    instances.get(0));
+        }
+        String instance = instances.get(0);
+        String classKey = AutoScalerOptions.customEvaluatorClassKey(instance);
+        String configuredClassName =
+                
conf.get(AutoScalerOptions.customEvaluatorClassOption(instance));
+        if (configuredClassName == null || configuredClassName.isBlank()) {
+            LOG.warn(
+                    "Custom metric evaluator instance '{}' is configured in 
'{}' but no implementation class is set via '{}'. "
+                            + "No custom metric evaluator will be applied.",
+                    instance,
+                    CUSTOM_EVALUATORS.key(),
+                    classKey);
+            return null;
+        }
+        ScalingMetricsEvaluatorPlugin match = null;
+        for (ScalingMetricsEvaluatorPlugin evaluator : customEvaluators) {
+            if (evaluator.getClass().getName().equals(configuredClassName)) {
+                match = evaluator;
+                break;
+            }
+        }
+        if (match == null) {
+            LOG.warn(
+                    "No registered custom metric evaluator matches class '{}' 
configured for instance '{}' via '{}'. Discovered evaluators: {}.",
+                    configuredClassName,
+                    instance,
+                    classKey,
+                    customEvaluators.stream()
+                            .map(e -> e.getClass().getName())
+                            .collect(Collectors.toList()));
+            return null;
+        }
+        return new Tuple2<>(match, 
AutoScalerOptions.customEvaluatorConfiguration(conf, instance));
+    }
+
     @VisibleForTesting
     protected static void mergeEvaluatedMetricsMaps(
             Map<ScalingMetric, EvaluatedScalingMetric> actual,
@@ -735,13 +805,6 @@ public class ScalingMetricEvaluator {
                                                                 
::mergeEvaluatedScalingMetric)));
     }
 
-    /**
-     * Merges two {@link EvaluatedScalingMetric} instances.
-     *
-     * @param actual The existing evaluated scaling metric.
-     * @param incoming The incoming evaluated scaling metric.
-     * @return A new {@link EvaluatedScalingMetric} instance with merged 
values.
-     */
     @VisibleForTesting
     protected static EvaluatedScalingMetric mergeEvaluatedScalingMetric(
             EvaluatedScalingMetric actual, EvaluatedScalingMetric incoming) {
diff --git 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/config/AutoScalerOptions.java
 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/config/AutoScalerOptions.java
index 8be3e0f10..c1d723e2e 100644
--- 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/config/AutoScalerOptions.java
+++ 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/config/AutoScalerOptions.java
@@ -22,7 +22,6 @@ import org.apache.flink.autoscaler.metrics.MetricAggregator;
 import org.apache.flink.configuration.ConfigOption;
 import org.apache.flink.configuration.ConfigOptions;
 import org.apache.flink.configuration.Configuration;
-import org.apache.flink.configuration.DelegatingConfiguration;
 import org.apache.flink.configuration.MemorySize;
 
 import java.time.Duration;
@@ -34,6 +33,7 @@ public class AutoScalerOptions {
     public static final String OLD_K8S_OP_CONF_PREFIX = "kubernetes.operator.";
     public static final String AUTOSCALER_CONF_PREFIX = "job.autoscaler.";
     public static final String CUSTOM_EVALUATOR_CONF_PREFIX = 
"metrics.custom-evaluator.";
+    public static final String CUSTOM_EVALUATOR_CLASS_SUFFIX = "class";
 
     private static String oldOperatorConfigKey(String key) {
         return OLD_K8S_OP_CONF_PREFIX + AUTOSCALER_CONF_PREFIX + key;
@@ -422,18 +422,116 @@ public class AutoScalerOptions {
                                     + "Prevents aggressive scaling by clamping 
low coefficient estimates. "
                                     + "If the estimated coefficient falls 
below this value, it is capped at the configured minimum.");
 
-    public static final ConfigOption<String> CUSTOM_EVALUATOR_NAME =
-            autoScalerConfig(CUSTOM_EVALUATOR_CONF_PREFIX + "name")
+    public static final ConfigOption<List<String>> CUSTOM_EVALUATORS =
+            autoScalerConfig("metrics.custom-evaluators")
                     .stringType()
-                    .defaultValue(null)
-                    
.withFallbackKeys(oldOperatorConfigKey(CUSTOM_EVALUATOR_CONF_PREFIX + "name"))
-                    .withDescription("Name of the custom evaluator to be 
used.");
+                    .asList()
+                    .defaultValues()
+                    
.withFallbackKeys(oldOperatorConfigKey("metrics.custom-evaluators"))
+                    .withDescription(
+                            "List of named custom metric evaluator instances 
to register with the autoscaler. "
+                                    + "For each <name> entry, the 
implementation class to instantiate must be set via "
+                                    + 
"'job.autoscaler.metrics.custom-evaluator.<name>.class', and additional 
per-instance "
+                                    + "options live under 
'job.autoscaler.metrics.custom-evaluator.<name>.<option>'. "
+                                    + "Only a single instance is honored for 
now: if more than one is configured, the autoscaler "
+                                    + "logs a warning and uses the first 
entry, ignoring the rest. "
+                                    + "Multi-instance support, including a 
priority/ordering contract, will be added as a follow-up.");
+
+    /**
+     * Documentation-only template option describing the per-instance class 
FQN key. Not read at
+     * runtime. The {@link #customEvaluatorClassKey(String)} is used to derive 
the actual key for a
+     * given evaluator instance.
+     */
+    @SuppressWarnings("unused")
+    public static final ConfigOption<String> CUSTOM_EVALUATOR_CLASS_TEMPLATE =
+            autoScalerConfig("metrics.custom-evaluator.<name>.class")
+                    .stringType()
+                    .noDefaultValue()
+                    
.withFallbackKeys(oldOperatorConfigKey("metrics.custom-evaluator.<name>.class"))
+                    .withDescription(
+                            "Fully-qualified class name of the implementation 
to instantiate for the custom metric evaluator "
+                                    + "instance <name> declared in 
'job.autoscaler.metrics.custom-evaluators'. "
+                                    + "The class must be discovered via the 
Plugin / ServiceLoader mechanism and registered "
+                                    + "under 
META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin.");
+
+    /**
+     * Documentation-only template option describing the per-instance 
free-form parameter keys. Not
+     * read at runtime. The actual evaluator parameters are surfaced to the 
evaluator via {@link
+     * #customEvaluatorConfiguration(Configuration, String)}.
+     */
+    @SuppressWarnings("unused")
+    public static final ConfigOption<String> 
CUSTOM_EVALUATOR_PARAMETER_TEMPLATE =
+            autoScalerConfig("metrics.custom-evaluator.<name>.<parameter>")
+                    .stringType()
+                    .noDefaultValue()
+                    .withFallbackKeys(
+                            
oldOperatorConfigKey("metrics.custom-evaluator.<name>.<parameter>"))
+                    .withDescription(
+                            "Free-form, evaluator-specific options for the 
custom metric evaluator instance <name>. "
+                                    + "All keys with this prefix are passed to 
the evaluator at runtime with the "
+                                    + 
"'job.autoscaler.metrics.custom-evaluator.<name>.' prefix stripped.");
+
+    public static String customEvaluatorClassKey(String instanceName) {
+        return AUTOSCALER_CONF_PREFIX
+                + CUSTOM_EVALUATOR_CONF_PREFIX
+                + instanceName
+                + "."
+                + CUSTOM_EVALUATOR_CLASS_SUFFIX;
+    }
+
+    public static String customEvaluatorClassFallbackKey(String instanceName) {
+        return OLD_K8S_OP_CONF_PREFIX + customEvaluatorClassKey(instanceName);
+    }
+
+    /**
+     * Builds the {@link ConfigOption} used to read the implementation class 
FQN for the given
+     * custom metric evaluator instance, including the legacy {@code 
kubernetes.operator.} prefix as
+     * a fallback key (canonical key takes precedence on overlap).
+     */
+    public static ConfigOption<String> customEvaluatorClassOption(String 
instanceName) {
+        return ConfigOptions.key(customEvaluatorClassKey(instanceName))
+                .stringType()
+                .noDefaultValue()
+                
.withFallbackKeys(customEvaluatorClassFallbackKey(instanceName));
+    }
 
-    public static Configuration forCustomEvaluator(
+    /**
+     * Builds a fresh, prefix-stripped {@link Configuration} that exposes the 
per-instance options
+     * configured for the scaling custom metric evaluator instance identified 
by {@code
+     * instanceName} under both the canonical ({@code
+     * job.autoscaler.metrics.custom-evaluator.<name>.}) and the legacy ({@code
+     * kubernetes.operator.job.autoscaler.metrics.custom-evaluator.<name>.}) 
namespaces. The legacy
+     * prefix is applied first so that, on overlap, values written under the 
canonical prefix take
+     * precedence, matching the semantics of {@link
+     * org.apache.flink.configuration.ConfigOption#withFallbackKeys} used 
elsewhere in this class.
+     */
+    public static Configuration customEvaluatorConfiguration(
             Configuration configuration, String customEvaluatorName) {
-        // add support for fallBackKey with DelegatingConfiguration.
-        return new DelegatingConfiguration(
-                configuration,
-                AUTOSCALER_CONF_PREFIX + CUSTOM_EVALUATOR_CONF_PREFIX + 
customEvaluatorName + ".");
+        String canonicalPrefix =
+                AUTOSCALER_CONF_PREFIX + CUSTOM_EVALUATOR_CONF_PREFIX + 
customEvaluatorName + ".";
+        String legacyPrefix =
+                OLD_K8S_OP_CONF_PREFIX
+                        + AUTOSCALER_CONF_PREFIX
+                        + CUSTOM_EVALUATOR_CONF_PREFIX
+                        + customEvaluatorName
+                        + ".";
+        Configuration merged = new Configuration();
+        configuration
+                .toMap()
+                .forEach(
+                        (k, v) -> {
+                            if (k.startsWith(legacyPrefix)) {
+                                
merged.setString(k.substring(legacyPrefix.length()), v);
+                            }
+                        });
+        configuration
+                .toMap()
+                .forEach(
+                        (k, v) -> {
+                            if (k.startsWith(canonicalPrefix)) {
+                                
merged.setString(k.substring(canonicalPrefix.length()), v);
+                            }
+                        });
+        return merged;
     }
 }
diff --git 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/metrics/FlinkAutoscalerEvaluator.java
 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/metrics/FlinkAutoscalerEvaluator.java
deleted file mode 100644
index 976f79b62..000000000
--- 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/metrics/FlinkAutoscalerEvaluator.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.flink.autoscaler.metrics;
-
-import org.apache.flink.autoscaler.topology.JobTopology;
-import org.apache.flink.configuration.Configuration;
-import org.apache.flink.core.plugin.Plugin;
-import org.apache.flink.runtime.jobgraph.JobVertexID;
-
-import lombok.Getter;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Map;
-import java.util.SortedMap;
-
-/**
- * Interface for custom evaluators that allow tailored scaling metric 
evaluations. Implementations of
- * this interface can provide custom logic to evaluate vertex metrics and 
merge them with internally
- * evaluated metrics.
- */
-public interface FlinkAutoscalerEvaluator extends Plugin {
-
-    /**
-     * Returns the name of the custom evaluator.
-     *
-     * @return The name of the custom evaluator.
-     */
-    String getName();
-
-    /**
-     * Evaluates scaling metrics for a given job vertex based on the 
internally evaluated metrics
-     * and context.
-     *
-     * @param vertex The {@link JobVertexID} identifying the vertex whose 
metrics are being
-     *     evaluated.
-     * @param evaluatedMetrics An un-modifiable view of current vertex 
internally evaluated metrics.
-     * @param evaluationContext The evaluation context providing job-related 
configurations and
-     *     historical metrics.
-     * @return A map of evaluated scaling metrics for the vertex which would 
get merged with
-     *     internally evaluated metrics.
-     * @throws UnsupportedOperationException if an attempt is made to modify 
the {@code
-     *     evaluatedMetrics}, {@code Context.jobConf}, {@code 
Context.metricsHistory}, {@code
-     *     Context.evaluatedVertexMetrics}.
-     */
-    Map<ScalingMetric, EvaluatedScalingMetric> evaluateVertexMetrics(
-            JobVertexID vertex,
-            Map<ScalingMetric, EvaluatedScalingMetric> evaluatedMetrics,
-            Context evaluationContext);
-
-    /**
-     * Context providing relevant job and metric information to assist in 
custom metric evaluation.
-     */
-    @Getter
-    class Context {
-        private final Configuration jobConf;
-
-        private final SortedMap<Instant, CollectedMetrics> metricsHistory;
-
-        private final Map<JobVertexID, Map<ScalingMetric, 
EvaluatedScalingMetric>>
-                evaluatedVertexMetrics;
-
-        private final JobTopology topology;
-
-        private final boolean processingBacklog;
-
-        private final Duration restartTime;
-
-        private final Configuration customEvaluatorConf;
-
-        /**
-         * Constructs a new {@link Context} instance.
-         *
-         * @param jobConf An un-modifiable view of job's configuration.
-         * @param metricsHistory An un-modifiable view of historical record of 
collected metrics,
-         *     ordered by timestamp.
-         * @param evaluatedVertexMetrics This map contains an un-modifiable 
view of evaluated
-         *     metrics for previously evaluated vertex. Note: evaluation of 
Vertex for scaling
-         *     metrics happens topologically.
-         * @param topology The job topology representing the structure of the 
Flink job.
-         * @param processingBacklog Indicates whether the job is processing 
backlog.
-         * @param restartTime Maximum restart time based on scaling records.
-         * @param customEvaluatorConf The configuration associated with the 
custom evaluator.
-         */
-        public Context(
-                Configuration jobConf,
-                SortedMap<Instant, CollectedMetrics> metricsHistory,
-                Map<JobVertexID, Map<ScalingMetric, EvaluatedScalingMetric>> 
evaluatedVertexMetrics,
-                JobTopology topology,
-                boolean processingBacklog,
-                Duration restartTime,
-                Configuration customEvaluatorConf) {
-            this.jobConf = jobConf;
-            this.metricsHistory = metricsHistory;
-            this.evaluatedVertexMetrics = evaluatedVertexMetrics;
-            this.topology = topology;
-            this.processingBacklog = processingBacklog;
-            this.restartTime = restartTime;
-            this.customEvaluatorConf = customEvaluatorConf;
-        }
-    }
-}
diff --git 
a/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/metrics/ScalingMetricsEvaluatorPlugin.java
 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/metrics/ScalingMetricsEvaluatorPlugin.java
new file mode 100644
index 000000000..59d9978de
--- /dev/null
+++ 
b/flink-autoscaler/src/main/java/org/apache/flink/autoscaler/metrics/ScalingMetricsEvaluatorPlugin.java
@@ -0,0 +1,114 @@
+/*
+ * 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.flink.autoscaler.metrics;
+
+import org.apache.flink.annotation.Experimental;
+import org.apache.flink.autoscaler.topology.JobTopology;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.runtime.jobgraph.JobVertexID;
+
+import lombok.Value;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * A pluggable plugin that allows users to provide custom scaling-metric 
evaluation logic on top of
+ * the metrics evaluated internally by the autoscaler. Implementations are 
invoked once per job
+ * vertex during each evaluation cycle, and the metrics they return are merged 
on top of the
+ * internally evaluated metrics, allowing users to override or augment 
specific {@link
+ * ScalingMetric} values.
+ *
+ * <p>Only one custom metric evaluator per pipeline is supported for now. If 
multiple instances are
+ * configured, the autoscaler logs a warning and falls back to the first 
entry, ignoring the rest.
+ * Registering multiple implementations via {@code META-INF/services} is fine 
as they form a
+ * registry that different jobs can select from by class FQN, but a single job 
cannot chain or
+ * compose more than one evaluator.
+ *
+ * <p>This was introduced as part of <a
+ * 
href="https://cwiki.apache.org/confluence/display/FLINK/FLIP-514%3A+Custom+Evaluator+plugin+for+Flink+Autoscaler";>FLIP-514:
+ * Custom Evaluator plugin for Flink Autoscaler</a> and is complementary to <a
+ * 
href="https://cwiki.apache.org/confluence/display/FLINK/FLIP-575%3A+Scaling+Executor+Plugin+SPI+for+Flink+Autoscaler";>FLIP-575:
+ * Scaling Executor Plugin SPI for Flink Autoscaler</a> which provides 
extensibility at the scaling
+ * decision execution layer.
+ *
+ * <p>Implementations are discovered via Java's {@link 
java.util.ServiceLoader} mechanism. To
+ * register a custom metric evaluator, add the fully qualified class name of 
the implementation to
+ * {@code 
META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin}.
+ */
+@Experimental
+public interface ScalingMetricsEvaluatorPlugin {
+
+    /**
+     * Evaluates scaling metrics for a given job vertex based on the 
internally evaluated metrics
+     * and context.
+     *
+     * @param vertex The {@link JobVertexID} identifying the vertex whose 
metrics are being
+     *     evaluated.
+     * @param evaluatedMetrics An un-modifiable view of current vertex 
internally evaluated metrics.
+     * @param evaluationContext The evaluation context providing job-related 
configurations and
+     *     historical metrics.
+     * @return A map of evaluated scaling metrics for the vertex which would 
get merged with
+     *     internally evaluated metrics.
+     * @throws UnsupportedOperationException if an attempt is made to modify 
the {@code
+     *     evaluatedMetrics}, {@code Context.config}, {@code 
Context.metricsHistory}, {@code
+     *     Context.evaluatedVertexMetrics}.
+     */
+    Map<ScalingMetric, EvaluatedScalingMetric> evaluateVertexMetrics(
+            JobVertexID vertex,
+            Map<ScalingMetric, EvaluatedScalingMetric> evaluatedMetrics,
+            Context evaluationContext);
+
+    /**
+     * Context providing relevant job and metric information to assist in 
custom metric evaluation.
+     */
+    @Value
+    class Context {
+        /**
+         * An un-modifiable view of the job configuration, with the 
evaluator-specific options
+         * (configured under {@code 
job.autoscaler.metrics.custom-evaluator.<name>.}) merged on top
+         * with their prefix stripped. Evaluator-specific keys take precedence 
over the job-level
+         * value of the same key, so a CR can override behaviour for this 
evaluator alone.
+         */
+        Configuration config;
+
+        /**
+         * An un-modifiable view of the historical record of collected 
metrics, ordered by
+         * timestamp.
+         */
+        SortedMap<Instant, CollectedMetrics> metricsHistory;
+
+        /**
+         * An un-modifiable view of evaluated metrics for previously evaluated 
vertices. Note:
+         * evaluation of a vertex for scaling metrics happens topologically.
+         */
+        Map<JobVertexID, Map<ScalingMetric, EvaluatedScalingMetric>> 
evaluatedVertexMetrics;
+
+        /** The job topology representing the structure of the Flink job. */
+        JobTopology topology;
+
+        /** Indicates whether the job is processing backlog. */
+        boolean processingBacklog;
+
+        /** Maximum restart time based on scaling records. */
+        Duration restartTime;
+    }
+}
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/AutoScalerCustomEvaluatorTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/AutoScalerCustomEvaluatorTest.java
index 00bf08e37..a7b5418cf 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/AutoScalerCustomEvaluatorTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/AutoScalerCustomEvaluatorTest.java
@@ -22,7 +22,7 @@ import org.apache.flink.api.common.JobID;
 import org.apache.flink.autoscaler.config.AutoScalerOptions;
 import org.apache.flink.autoscaler.event.TestingEventCollector;
 import org.apache.flink.autoscaler.metrics.AutoscalerFlinkMetrics;
-import org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator;
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
 import org.apache.flink.autoscaler.metrics.TestCustomEvaluator;
 import org.apache.flink.autoscaler.metrics.TestMetrics;
 import org.apache.flink.autoscaler.realizer.TestingScalingRealizer;
@@ -31,6 +31,7 @@ import 
org.apache.flink.autoscaler.state.InMemoryAutoScalerStateStore;
 import org.apache.flink.autoscaler.topology.IOMetrics;
 import org.apache.flink.autoscaler.topology.JobTopology;
 import org.apache.flink.autoscaler.topology.VertexInfo;
+import org.apache.flink.configuration.ConfigOptions;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
 
@@ -41,13 +42,18 @@ import java.time.Clock;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
+import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import static 
org.apache.flink.autoscaler.TestingAutoscalerUtils.createDefaultJobAutoScalerContext;
 import static org.apache.flink.autoscaler.topology.ShipStrategy.REBALANCE;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-/** Unit test for testing the integration of custom evaluators with the Flink 
job autoscaler. */
+/**
+ * Unit test for testing the integration of custom metric evaluators with the 
Flink job autoscaler.
+ */
 public class AutoScalerCustomEvaluatorTest {
     private JobAutoScalerContext<JobID> context;
     private AutoScalerStateStore<JobID, JobAutoScalerContext<JobID>> 
stateStore;
@@ -100,17 +106,22 @@ public class AutoScalerCustomEvaluatorTest {
         defaultConf.set(AutoScalerOptions.SCALE_DOWN_INTERVAL, Duration.ZERO);
         defaultConf.set(AutoScalerOptions.BACKLOG_PROCESSING_LAG_THRESHOLD, 
Duration.ofSeconds(1));
 
-        defaultConf.set(AutoScalerOptions.CUSTOM_EVALUATOR_NAME, 
testCustomEvaluatorName);
+        defaultConf.set(AutoScalerOptions.CUSTOM_EVALUATORS, 
List.of(testCustomEvaluatorName));
+        defaultConf.set(
+                ConfigOptions.key(
+                                
AutoScalerOptions.customEvaluatorClassKey(testCustomEvaluatorName))
+                        .stringType()
+                        .noDefaultValue(),
+                TestCustomEvaluator.class.getName());
 
         autoscaler =
                 new JobAutoScalerImpl<>(
                         metricsCollector,
-                        new ScalingMetricEvaluator(),
+                        new ScalingMetricEvaluator(customEvaluators),
                         scalingExecutor,
                         eventCollector,
                         new TestingScalingRealizer<>(),
-                        stateStore,
-                        customEvaluators);
+                        stateStore);
 
         // Reset custom window size to default
         metricsCollector.setTestMetricWindowSize(null);
@@ -153,6 +164,95 @@ public class AutoScalerCustomEvaluatorTest {
         assertFlinkMetricsCount(1, 0);
     }
 
+    @Test
+    void testForCustomEvaluatorStripsCanonicalPrefix() {
+        Configuration conf = new Configuration();
+        conf.setString(
+                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                        + AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                        + "my-evaluator.target-data-rate",
+                "12345.0");
+        // Unrelated keys must not leak into the per-evaluator view.
+        conf.setString(
+                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                        + AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                        + "other-evaluator.foo",
+                "bar");
+        conf.setString("unrelated.key", "should-not-leak");
+
+        Configuration scoped = 
AutoScalerOptions.customEvaluatorConfiguration(conf, "my-evaluator");
+        assertThat(scoped.toMap())
+                .containsExactlyInAnyOrderEntriesOf(Map.of("target-data-rate", 
"12345.0"));
+    }
+
+    @Test
+    void testForCustomEvaluatorHonorsLegacyFallbackPrefix() {
+        Configuration conf = new Configuration();
+        // Only the legacy 'kubernetes.operator.' prefix is set, both for a 
free-form parameter
+        // and for the per-instance class FQN.
+        conf.setString(
+                AutoScalerOptions.OLD_K8S_OP_CONF_PREFIX
+                        + AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                        + AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                        + "my-evaluator.target-data-rate",
+                "999.0");
+        conf.setString(
+                
AutoScalerOptions.customEvaluatorClassFallbackKey("my-evaluator"),
+                "org.example.LegacyEvaluator");
+
+        Configuration scoped = 
AutoScalerOptions.customEvaluatorConfiguration(conf, "my-evaluator");
+        assertThat(scoped.toMap())
+                .containsExactlyInAnyOrderEntriesOf(
+                        Map.of(
+                                "target-data-rate", "999.0",
+                                "class", "org.example.LegacyEvaluator"));
+
+        // The class option must resolve via withFallbackKeys() against the 
legacy prefix.
+        
assertThat(conf.get(AutoScalerOptions.customEvaluatorClassOption("my-evaluator")))
+                .isEqualTo("org.example.LegacyEvaluator");
+    }
+
+    @Test
+    void testForCustomEvaluatorCanonicalPrefixWinsOverLegacyOnOverlap() {
+        Configuration conf = new Configuration();
+        // Both prefixes set for the same free-form option and for the class 
FQN -> canonical
+        // takes precedence in both cases.
+        conf.setString(
+                AutoScalerOptions.OLD_K8S_OP_CONF_PREFIX
+                        + AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                        + AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                        + "my-evaluator.target-data-rate",
+                "from-legacy");
+        conf.setString(
+                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                        + AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                        + "my-evaluator.target-data-rate",
+                "from-canonical");
+        conf.setString(
+                
AutoScalerOptions.customEvaluatorClassFallbackKey("my-evaluator"),
+                "org.example.LegacyEvaluator");
+        conf.setString(
+                AutoScalerOptions.customEvaluatorClassKey("my-evaluator"),
+                "org.example.CanonicalEvaluator");
+
+        Configuration scoped = 
AutoScalerOptions.customEvaluatorConfiguration(conf, "my-evaluator");
+        
assertThat(scoped.toMap().get("target-data-rate")).isEqualTo("from-canonical");
+        
assertThat(scoped.toMap().get("class")).isEqualTo("org.example.CanonicalEvaluator");
+
+        // The class option must prefer the canonical key over the legacy 
fallback on overlap.
+        
assertThat(conf.get(AutoScalerOptions.customEvaluatorClassOption("my-evaluator")))
+                .isEqualTo("org.example.CanonicalEvaluator");
+    }
+
+    @Test
+    void testForCustomEvaluatorReturnsEmptyConfigWhenNothingMatches() {
+        Configuration conf = new Configuration();
+        conf.setString("unrelated.key", "value");
+
+        Configuration scoped = 
AutoScalerOptions.customEvaluatorConfiguration(conf, "my-evaluator");
+        assertThat(scoped.toMap()).isEmpty();
+    }
+
     private void setClocksTo(Instant time) {
         var clock = Clock.fixed(time, ZoneId.systemDefault());
         autoscaler.setClock(clock);
@@ -165,9 +265,7 @@ public class AutoScalerCustomEvaluatorTest {
         assertEquals(balancedCount, 
autoscalerFlinkMetrics.getNumBalancedCount());
     }
 
-    private Map<String, FlinkAutoscalerEvaluator> createTestCustomEvaluator() {
-        var testCustomEvaluator = new TestCustomEvaluator();
-        testCustomEvaluator.configure(new Configuration());
-        return Map.of(testCustomEvaluator.getName(), testCustomEvaluator);
+    private Collection<ScalingMetricsEvaluatorPlugin> 
createTestCustomEvaluator() {
+        return List.of(new TestCustomEvaluator());
     }
 }
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/BacklogBasedScalingTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/BacklogBasedScalingTest.java
index 956ecf32d..9ff58ab21 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/BacklogBasedScalingTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/BacklogBasedScalingTest.java
@@ -40,7 +40,7 @@ import java.time.Clock;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
-import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
 
@@ -105,12 +105,11 @@ public class BacklogBasedScalingTest {
         autoscaler =
                 new JobAutoScalerImpl<>(
                         metricsCollector,
-                        new ScalingMetricEvaluator(),
+                        new ScalingMetricEvaluator(List.of()),
                         scalingExecutor,
                         eventCollector,
                         new TestingScalingRealizer<>(),
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         // Reset custom window size to default
         metricsCollector.setTestMetricWindowSize(null);
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/DelayedScaleDownEndToEndTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/DelayedScaleDownEndToEndTest.java
index 9eca91295..43c9f8a1b 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/DelayedScaleDownEndToEndTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/DelayedScaleDownEndToEndTest.java
@@ -118,12 +118,11 @@ public class DelayedScaleDownEndToEndTest {
         autoscaler =
                 new JobAutoScalerImpl<>(
                         metricsCollector,
-                        new ScalingMetricEvaluator(),
+                        new ScalingMetricEvaluator(List.of()),
                         new ScalingExecutor<>(eventCollector, stateStore),
                         eventCollector,
                         scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         // initially the last evaluated metrics are empty
         
assertThat(autoscaler.lastEvaluatedMetrics.get(context.getJobKey())).isNull();
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/JobAutoScalerImplTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/JobAutoScalerImplTest.java
index 1597ec5cc..384d97631 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/JobAutoScalerImplTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/JobAutoScalerImplTest.java
@@ -24,9 +24,7 @@ import 
org.apache.flink.autoscaler.event.TestingEventCollector;
 import org.apache.flink.autoscaler.exceptions.NotReadyException;
 import org.apache.flink.autoscaler.metrics.AutoscalerFlinkMetrics;
 import org.apache.flink.autoscaler.metrics.CollectedMetrics;
-import org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator;
 import org.apache.flink.autoscaler.metrics.ScalingMetric;
-import org.apache.flink.autoscaler.metrics.TestCustomEvaluator;
 import org.apache.flink.autoscaler.metrics.TestMetrics;
 import org.apache.flink.autoscaler.realizer.ScalingRealizer;
 import org.apache.flink.autoscaler.realizer.TestingScalingRealizer;
@@ -35,8 +33,6 @@ import org.apache.flink.autoscaler.topology.JobTopology;
 import org.apache.flink.autoscaler.topology.VertexInfo;
 import org.apache.flink.autoscaler.tuning.ConfigChanges;
 import org.apache.flink.client.program.rest.RestClusterClient;
-import org.apache.flink.configuration.ConfigOptions;
-import org.apache.flink.configuration.Configuration;
 import org.apache.flink.configuration.PipelineOptions;
 import org.apache.flink.configuration.TaskManagerOptions;
 import org.apache.flink.metrics.Gauge;
@@ -60,7 +56,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
 import java.util.TreeMap;
 
 import static java.util.Map.entry;
@@ -71,9 +66,6 @@ import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.VERTEX_SCALIN
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertInstanceOf;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /** Tests for JobAutoScalerImpl. */
@@ -111,7 +103,7 @@ public class JobAutoScalerImplTest {
                         .pendingRecords(0L)
                         .build());
 
-        ScalingMetricEvaluator evaluator = new ScalingMetricEvaluator();
+        ScalingMetricEvaluator evaluator = new 
ScalingMetricEvaluator(Collections.emptyList());
         ScalingExecutor<JobID, JobAutoScalerContext<JobID>> scalingExecutor =
                 new ScalingExecutor<>(eventCollector, stateStore);
 
@@ -122,8 +114,7 @@ public class JobAutoScalerImplTest {
                         scalingExecutor,
                         eventCollector,
                         scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         var now = Instant.now();
         autoscaler.setClock(Clock.fixed(now, ZoneId.systemDefault()));
@@ -162,13 +153,7 @@ public class JobAutoScalerImplTest {
     void testErrorReporting() throws Exception {
         var autoscaler =
                 new JobAutoScalerImpl<>(
-                        null,
-                        null,
-                        null,
-                        eventCollector,
-                        scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        null, null, null, eventCollector, scalingRealizer, 
stateStore);
 
         autoscaler.scale(context);
         Assertions.assertEquals(
@@ -203,8 +188,7 @@ public class JobAutoScalerImplTest {
                         null,
                         eventCollector,
                         scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         // Should not produce an error
         autoscaler.scale(context);
@@ -243,8 +227,7 @@ public class JobAutoScalerImplTest {
                         null,
                         eventCollector,
                         
realizeParallelismOverridesWithExceptionsScalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         // Should produce an error
         autoscaler.scale(context);
@@ -261,8 +244,7 @@ public class JobAutoScalerImplTest {
                         null,
                         eventCollector,
                         scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         // Initially we should return empty overrides, do not crate any state
         assertThat(autoscaler.getParallelismOverrides(context)).isEmpty();
@@ -336,13 +318,7 @@ public class JobAutoScalerImplTest {
         var overrides = new HashMap<String, String>();
         var autoscaler =
                 new JobAutoScalerImpl<>(
-                        null,
-                        null,
-                        null,
-                        eventCollector,
-                        scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap()) {
+                        null, null, null, eventCollector, scalingRealizer, 
stateStore) {
                     public Map<String, String> getParallelismOverrides(
                             JobAutoScalerContext<JobID> ctx) {
                         return new HashMap<>(overrides);
@@ -441,13 +417,7 @@ public class JobAutoScalerImplTest {
         
context.getConfiguration().set(AutoScalerOptions.MEMORY_TUNING_ENABLED, true);
         var autoscaler =
                 new JobAutoScalerImpl<>(
-                        null,
-                        null,
-                        null,
-                        eventCollector,
-                        scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        null, null, null, eventCollector, scalingRealizer, 
stateStore);
 
         // Initially we should return empty overrides, do not crate any state
         
assertThat(stateStore.getConfigChanges(context).getOverrides()).isEmpty();
@@ -495,13 +465,7 @@ public class JobAutoScalerImplTest {
 
         var autoscaler =
                 new JobAutoScalerImpl<>(
-                        null,
-                        null,
-                        null,
-                        eventCollector,
-                        scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
+                        null, null, null, eventCollector, scalingRealizer, 
stateStore);
         autoscaler.scale(context);
 
         assertTrue(stateStore.getScalingHistory(context).isEmpty());
@@ -526,109 +490,4 @@ public class JobAutoScalerImplTest {
     private TestingScalingRealizer.Event<JobID, JobAutoScalerContext<JobID>> 
getEvent() {
         return scalingRealizer.events.poll();
     }
-
-    @Test
-    void testGetCustomEvaluatorIfRequired() {
-        FlinkAutoscalerEvaluator testCustomEvaluator = new 
TestCustomEvaluator();
-        testCustomEvaluator.configure(new Configuration());
-        var testCustomEvaluators = Map.of(testCustomEvaluator.getName(), 
testCustomEvaluator);
-
-        var autoscalerWithCustomEvaluator =
-                new JobAutoScalerImpl<>(
-                        null,
-                        null,
-                        null,
-                        eventCollector,
-                        scalingRealizer,
-                        stateStore,
-                        testCustomEvaluators);
-
-        String testCustomEvaluatorName = "test-custom-evaluator";
-
-        var defaultConf = context.getConfiguration();
-
-        // Case 1: Custom evaluator configured.
-        defaultConf.set(AutoScalerOptions.CUSTOM_EVALUATOR_NAME, 
testCustomEvaluatorName);
-
-        var customEvaluatorWithConfig =
-                autoscalerWithCustomEvaluator.getCustomEvaluatorIfRequired(
-                        context.getConfiguration());
-        assertNotNull(customEvaluatorWithConfig);
-        assertInstanceOf(FlinkAutoscalerEvaluator.class, 
customEvaluatorWithConfig.f0);
-        var customEvaluatorConfig = customEvaluatorWithConfig.f1;
-        assertNotNull(customEvaluatorConfig);
-        assertEquals(0, customEvaluatorConfig.keySet().size());
-
-        Set<String> expectedKeys = Set.of();
-        assertEquals(expectedKeys, customEvaluatorConfig.keySet());
-
-        // Case 2: Custom evaluator configured with additional configs for the 
plugin.
-        defaultConf.set(
-                ConfigOptions.key(
-                                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
-                                        + 
AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
-                                        + testCustomEvaluatorName
-                                        + ".k1")
-                        .stringType()
-                        .noDefaultValue(),
-                "v1");
-
-        defaultConf.set(
-                ConfigOptions.key(
-                                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
-                                        + 
AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
-                                        + testCustomEvaluatorName
-                                        + ".k2")
-                        .stringType()
-                        .noDefaultValue(),
-                "v2");
-
-        var customEvaluatorWithConfigContainingAdditionalKeys =
-                autoscalerWithCustomEvaluator.getCustomEvaluatorIfRequired(
-                        context.getConfiguration());
-        assertNotNull(customEvaluatorWithConfigContainingAdditionalKeys);
-        assertInstanceOf(
-                FlinkAutoscalerEvaluator.class,
-                customEvaluatorWithConfigContainingAdditionalKeys.f0);
-        var customEvaluatorConfigContainingAdditionalKeys =
-                customEvaluatorWithConfigContainingAdditionalKeys.f1;
-        assertNotNull(customEvaluatorConfigContainingAdditionalKeys);
-        assertEquals(2, 
customEvaluatorConfigContainingAdditionalKeys.keySet().size());
-
-        expectedKeys = Set.of("k1", "k2");
-        assertEquals(expectedKeys, 
customEvaluatorConfigContainingAdditionalKeys.keySet());
-
-        // Case 3: Custom evaluator configured but with a custom evaluator 
name that is not
-        // available.
-        defaultConf.set(AutoScalerOptions.CUSTOM_EVALUATOR_NAME, 
"test-custom-evaluator-no-match");
-
-        var customEvaluatorWithConfigNoMatch =
-                autoscalerWithCustomEvaluator.getCustomEvaluatorIfRequired(
-                        context.getConfiguration());
-        assertNull(customEvaluatorWithConfigNoMatch);
-
-        // Case 4: Custom evaluator not configured.
-        defaultConf.removeConfig(AutoScalerOptions.CUSTOM_EVALUATOR_NAME);
-
-        var customEvaluatorNotConfigured =
-                autoscalerWithCustomEvaluator.getCustomEvaluatorIfRequired(
-                        context.getConfiguration());
-        assertNull(customEvaluatorNotConfigured);
-
-        // Case 5: No custom evaluators available.
-        var autoscalerWithoutCustomEvaluator =
-                new JobAutoScalerImpl<>(
-                        null,
-                        null,
-                        null,
-                        eventCollector,
-                        scalingRealizer,
-                        stateStore,
-                        Collections.emptyMap());
-
-        var customEvaluatorConfigNoCustomEvaluators =
-                autoscalerWithoutCustomEvaluator.getCustomEvaluatorIfRequired(
-                        context.getConfiguration());
-        assertNull(customEvaluatorConfigNoCustomEvaluators);
-    }
 }
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/MetricsCollectionAndEvaluationTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/MetricsCollectionAndEvaluationTest.java
index a046d3be3..c347cff8a 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/MetricsCollectionAndEvaluationTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/MetricsCollectionAndEvaluationTest.java
@@ -80,7 +80,7 @@ public class MetricsCollectionAndEvaluationTest {
     @BeforeEach
     public void setup() {
         context = createDefaultJobAutoScalerContext();
-        evaluator = new ScalingMetricEvaluator();
+        evaluator = new ScalingMetricEvaluator(List.of());
         stateStore = new InMemoryAutoScalerStateStore<>();
         scalingExecutor = new ScalingExecutor<>(new TestingEventCollector<>(), 
stateStore);
 
@@ -181,7 +181,7 @@ public class MetricsCollectionAndEvaluationTest {
         assertEquals(3, collectedMetrics.getMetricHistory().size());
         assertTrue(collectedMetrics.isFullyCollected());
 
-        var evaluation = evaluator.evaluate(conf, collectedMetrics, 
restartTime, null);
+        var evaluation = evaluator.evaluate(conf, collectedMetrics, 
restartTime);
         scalingExecutor.scaleResource(
                 context,
                 evaluation,
@@ -401,7 +401,7 @@ public class MetricsCollectionAndEvaluationTest {
         var collectedMetrics = metricsCollector.updateMetrics(context, 
stateStore);
 
         var evaluation =
-                evaluator.evaluate(context.getConfiguration(), 
collectedMetrics, restartTime, null);
+                evaluator.evaluate(context.getConfiguration(), 
collectedMetrics, restartTime);
         assertEquals(
                 500.,
                 evaluation
@@ -661,7 +661,7 @@ public class MetricsCollectionAndEvaluationTest {
         var collectedMetrics = collectMetrics();
 
         var evaluation =
-                evaluator.evaluate(context.getConfiguration(), 
collectedMetrics, restartTime, null);
+                evaluator.evaluate(context.getConfiguration(), 
collectedMetrics, restartTime);
         assertEquals(
                 0,
                 evaluation
@@ -712,8 +712,7 @@ public class MetricsCollectionAndEvaluationTest {
                 .getMetricHistory()
                 .put(Instant.ofEpochSecond(1234), new 
CollectedMetrics(newMetrics, Map.of()));
 
-        evaluation =
-                evaluator.evaluate(context.getConfiguration(), 
collectedMetrics, restartTime, null);
+        evaluation = evaluator.evaluate(context.getConfiguration(), 
collectedMetrics, restartTime);
         assertEquals(
                 3.,
                 evaluation
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/RecommendedParallelismTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/RecommendedParallelismTest.java
index 2902d25fa..99cdc8ead 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/RecommendedParallelismTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/RecommendedParallelismTest.java
@@ -37,7 +37,7 @@ import java.time.Clock;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
-import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 import static 
org.apache.flink.autoscaler.TestingAutoscalerUtils.createDefaultJobAutoScalerContext;
@@ -95,12 +95,11 @@ public class RecommendedParallelismTest {
         autoscaler =
                 new JobAutoScalerImpl<>(
                         metricsCollector,
-                        new ScalingMetricEvaluator(),
+                        new ScalingMetricEvaluator(List.of()),
                         new ScalingExecutor<>(eventCollector, stateStore),
                         eventCollector,
                         new TestingScalingRealizer<>(),
-                        stateStore,
-                        Collections.emptyMap());
+                        stateStore);
 
         // Reset custom window size to default
         metricsCollector.setTestMetricWindowSize(null);
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/ScalingMetricEvaluatorTest.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/ScalingMetricEvaluatorTest.java
index b17295e1d..d914337f2 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/ScalingMetricEvaluatorTest.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/ScalingMetricEvaluatorTest.java
@@ -22,12 +22,14 @@ import org.apache.flink.autoscaler.config.AutoScalerOptions;
 import org.apache.flink.autoscaler.metrics.CollectedMetricHistory;
 import org.apache.flink.autoscaler.metrics.CollectedMetrics;
 import org.apache.flink.autoscaler.metrics.EvaluatedScalingMetric;
-import org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator;
 import org.apache.flink.autoscaler.metrics.MetricAggregator;
 import org.apache.flink.autoscaler.metrics.ScalingMetric;
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
 import org.apache.flink.autoscaler.metrics.TestCustomEvaluator;
 import org.apache.flink.autoscaler.topology.JobTopology;
 import org.apache.flink.autoscaler.topology.VertexInfo;
+import org.apache.flink.configuration.ConfigOption;
+import org.apache.flink.configuration.ConfigOptions;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.configuration.UnmodifiableConfiguration;
 import org.apache.flink.runtime.jobgraph.JobVertexID;
@@ -38,7 +40,9 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 
 import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.CATCH_UP_DURATION;
@@ -48,12 +52,15 @@ import static 
org.apache.flink.autoscaler.config.AutoScalerOptions.UTILIZATION_T
 import static org.apache.flink.autoscaler.topology.ShipStrategy.REBALANCE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /** Scaling evaluator test. */
 public class ScalingMetricEvaluatorTest {
 
-    private ScalingMetricEvaluator evaluator = new ScalingMetricEvaluator();
+    private ScalingMetricEvaluator evaluator = new 
ScalingMetricEvaluator(List.of());
 
     @Test
     public void testLagBasedSourceScaling() {
@@ -115,8 +122,7 @@ public class ScalingMetricEvaluatorTest {
                         .evaluate(
                                 conf,
                                 new CollectedMetricHistory(topology, 
metricHistory, Instant.now()),
-                                Duration.ZERO,
-                                null)
+                                Duration.ZERO)
                         .getVertexMetrics();
 
         assertEquals(
@@ -150,8 +156,7 @@ public class ScalingMetricEvaluatorTest {
                         .evaluate(
                                 conf,
                                 new CollectedMetricHistory(topology, 
metricHistory, Instant.now()),
-                                Duration.ZERO,
-                                null)
+                                Duration.ZERO)
                         .getVertexMetrics();
         assertEquals(
                 EvaluatedScalingMetric.avg(150),
@@ -174,8 +179,7 @@ public class ScalingMetricEvaluatorTest {
                         .evaluate(
                                 conf,
                                 new CollectedMetricHistory(topology, 
metricHistory, Instant.now()),
-                                Duration.ZERO,
-                                null)
+                                Duration.ZERO)
                         .getVertexMetrics();
         assertEquals(
                 EvaluatedScalingMetric.avg(150),
@@ -197,8 +201,7 @@ public class ScalingMetricEvaluatorTest {
                         .evaluate(
                                 conf,
                                 new CollectedMetricHistory(topology, 
metricHistory, Instant.now()),
-                                Duration.ZERO,
-                                null)
+                                Duration.ZERO)
                         .getVertexMetrics();
         assertEquals(
                 EvaluatedScalingMetric.avg(150),
@@ -257,8 +260,7 @@ public class ScalingMetricEvaluatorTest {
                         .evaluate(
                                 conf,
                                 new CollectedMetricHistory(topology, 
metricHistory, Instant.now()),
-                                Duration.ZERO,
-                                null)
+                                Duration.ZERO)
                         .getVertexMetrics();
         assertEquals(
                 EvaluatedScalingMetric.avg(100),
@@ -319,16 +321,20 @@ public class ScalingMetricEvaluatorTest {
         var conf = new Configuration();
 
         conf.set(CATCH_UP_DURATION, Duration.ofSeconds(2));
-        FlinkAutoscalerEvaluator customEvaluator = new TestCustomEvaluator();
-        var customEvaluatorWithConfig = new Tuple2<>(customEvaluator, new 
Configuration());
+        ScalingMetricsEvaluatorPlugin customEvaluator = new 
TestCustomEvaluator();
+        var customEvaluatorName = "test-custom-evaluator";
+        conf.set(AutoScalerOptions.CUSTOM_EVALUATORS, 
List.of(customEvaluatorName));
+        conf.set(
+                
AutoScalerOptions.customEvaluatorClassOption(customEvaluatorName),
+                TestCustomEvaluator.class.getName());
+        var customEvaluatorAwareEvaluator = new 
ScalingMetricEvaluator(List.of(customEvaluator));
 
         var evaluatedMetrics =
-                evaluator
+                customEvaluatorAwareEvaluator
                         .evaluate(
                                 conf,
                                 new CollectedMetricHistory(topology, 
metricHistory, Instant.now()),
-                                Duration.ZERO,
-                                customEvaluatorWithConfig)
+                                Duration.ZERO)
                         .getVertexMetrics();
 
         assertEquals(
@@ -1031,20 +1037,19 @@ public class ScalingMetricEvaluatorTest {
                         Map.of()));
 
         var conf = new Configuration();
-        FlinkAutoscalerEvaluator customEvaluator = new TestCustomEvaluator();
+        ScalingMetricsEvaluatorPlugin customEvaluator = new 
TestCustomEvaluator();
         var evaluatedMetrics = new HashMap<ScalingMetric, 
EvaluatedScalingMetric>();
 
         var testCustomEvaluationSession =
                 Tuple2.of(
                         customEvaluator,
-                        new FlinkAutoscalerEvaluator.Context(
+                        new ScalingMetricsEvaluatorPlugin.Context(
                                 new UnmodifiableConfiguration(conf),
                                 
Collections.unmodifiableSortedMap(metricHistory),
                                 Collections.unmodifiableMap(new HashMap<>()),
                                 topology,
                                 false,
-                                Duration.ZERO,
-                                new Configuration()));
+                                Duration.ZERO));
 
         var testCustomEvaluatorResult =
                 ScalingMetricEvaluator.runCustomEvaluator(
@@ -1132,4 +1137,118 @@ public class ScalingMetricEvaluatorTest {
         assertEquals(60.0, result.getCurrent());
         assertEquals(120.0, result.getAverage());
     }
+
+    @Test
+    void testGetCustomEvaluatorIfRequired() {
+        ScalingMetricsEvaluatorPlugin testCustomEvaluator = new 
TestCustomEvaluator();
+        var customEvaluatorAwareEvaluator =
+                new ScalingMetricEvaluator(List.of(testCustomEvaluator));
+
+        String testCustomEvaluatorName = "test-custom-evaluator";
+        String testCustomEvaluatorClassName = 
TestCustomEvaluator.class.getName();
+        ConfigOption<String> classOpt =
+                ConfigOptions.key(
+                                
AutoScalerOptions.customEvaluatorClassKey(testCustomEvaluatorName))
+                        .stringType()
+                        .noDefaultValue();
+
+        var conf = new Configuration();
+
+        // Case 1: Single custom evaluator instance configured with its FQN 
class.
+        conf.set(AutoScalerOptions.CUSTOM_EVALUATORS, 
List.of(testCustomEvaluatorName));
+        conf.set(classOpt, testCustomEvaluatorClassName);
+
+        var customEvaluatorWithConfig =
+                
customEvaluatorAwareEvaluator.getCustomEvaluatorIfRequired(conf);
+        assertNotNull(customEvaluatorWithConfig);
+        assertInstanceOf(ScalingMetricsEvaluatorPlugin.class, 
customEvaluatorWithConfig.f0);
+        assertEquals(
+                testCustomEvaluatorClassName, 
customEvaluatorWithConfig.f0.getClass().getName());
+        var customEvaluatorConfig = customEvaluatorWithConfig.f1;
+        assertNotNull(customEvaluatorConfig);
+        // Only the .class key lives under the instance namespace at this 
point.
+        assertEquals(Set.of("class"), customEvaluatorConfig.keySet());
+
+        // Case 2: Custom evaluator with additional per-instance options.
+        conf.set(
+                ConfigOptions.key(
+                                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                                        + 
AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                                        + testCustomEvaluatorName
+                                        + ".k1")
+                        .stringType()
+                        .noDefaultValue(),
+                "v1");
+        conf.set(
+                ConfigOptions.key(
+                                AutoScalerOptions.AUTOSCALER_CONF_PREFIX
+                                        + 
AutoScalerOptions.CUSTOM_EVALUATOR_CONF_PREFIX
+                                        + testCustomEvaluatorName
+                                        + ".k2")
+                        .stringType()
+                        .noDefaultValue(),
+                "v2");
+
+        var customEvaluatorWithConfigContainingAdditionalKeys =
+                
customEvaluatorAwareEvaluator.getCustomEvaluatorIfRequired(conf);
+        assertNotNull(customEvaluatorWithConfigContainingAdditionalKeys);
+        assertInstanceOf(
+                ScalingMetricsEvaluatorPlugin.class,
+                customEvaluatorWithConfigContainingAdditionalKeys.f0);
+        var customEvaluatorConfigContainingAdditionalKeys =
+                customEvaluatorWithConfigContainingAdditionalKeys.f1;
+        assertNotNull(customEvaluatorConfigContainingAdditionalKeys);
+        assertEquals(
+                Set.of("class", "k1", "k2"),
+                customEvaluatorConfigContainingAdditionalKeys.keySet());
+
+        // Case 3: Configured class FQN does not match any registered 
evaluator -> null + warn.
+        conf.set(classOpt, 
"org.apache.flink.autoscaler.metrics.UnknownEvaluator");
+        
assertNull(customEvaluatorAwareEvaluator.getCustomEvaluatorIfRequired(conf));
+
+        // Case 4: Instance listed but no .class option set -> null + warn.
+        conf.removeConfig(classOpt);
+        
assertNull(customEvaluatorAwareEvaluator.getCustomEvaluatorIfRequired(conf));
+
+        // Case 5: Custom evaluators list not configured at all.
+        conf.removeConfig(AutoScalerOptions.CUSTOM_EVALUATORS);
+        
assertNull(customEvaluatorAwareEvaluator.getCustomEvaluatorIfRequired(conf));
+
+        // Case 6: More than one instance configured -> warn + first-wins 
(single-instance
+        // constraint).
+        conf.set(
+                AutoScalerOptions.CUSTOM_EVALUATORS,
+                List.of(testCustomEvaluatorName, "another-instance"));
+        conf.set(classOpt, testCustomEvaluatorClassName);
+        var firstWinsResult = 
customEvaluatorAwareEvaluator.getCustomEvaluatorIfRequired(conf);
+        assertNotNull(firstWinsResult);
+        assertEquals(testCustomEvaluatorClassName, 
firstWinsResult.f0.getClass().getName());
+        conf.removeConfig(AutoScalerOptions.CUSTOM_EVALUATORS);
+        conf.removeConfig(classOpt);
+
+        // Case 7: No custom evaluators registered at all -> null even when 
configured.
+        var noCustomEvaluator = new 
ScalingMetricEvaluator(Collections.emptyList());
+        conf.set(AutoScalerOptions.CUSTOM_EVALUATORS, 
List.of(testCustomEvaluatorName));
+        conf.set(classOpt, testCustomEvaluatorClassName);
+        assertNull(noCustomEvaluator.getCustomEvaluatorIfRequired(conf));
+    }
+
+    @Test
+    void testMergeEvaluatorConfig() {
+        var jobConf = new Configuration();
+        jobConf.setString("only-job", "j");
+        jobConf.setString("shared", "job-value");
+
+        var evaluatorConf = new Configuration();
+        evaluatorConf.setString("only-evaluator", "e");
+        evaluatorConf.setString("shared", "evaluator-value");
+
+        var merged = ScalingMetricEvaluator.mergeEvaluatorConfig(jobConf, 
evaluatorConf);
+
+        // Job-only and evaluator-only keys are both visible.
+        assertEquals("j", merged.getString("only-job", null));
+        assertEquals("e", merged.getString("only-evaluator", null));
+        // On a key present in both, the evaluator-specific value wins.
+        assertEquals("evaluator-value", merged.getString("shared", null));
+    }
 }
diff --git 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/metrics/TestCustomEvaluator.java
 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/metrics/TestCustomEvaluator.java
index b1426f6bc..4db951294 100644
--- 
a/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/metrics/TestCustomEvaluator.java
+++ 
b/flink-autoscaler/src/test/java/org/apache/flink/autoscaler/metrics/TestCustomEvaluator.java
@@ -25,14 +25,10 @@ import java.util.HashMap;
 import java.util.Map;
 
 /**
- * A test implementation of the {@link FlinkAutoscalerEvaluator} interface 
that provides
- * custom scaling metric evaluations for job vertices in a Flink job.
+ * A test implementation of the {@link ScalingMetricsEvaluatorPlugin} 
interface that provides custom
+ * scaling metric evaluations for job vertices in a Flink job.
  */
-public class TestCustomEvaluator implements FlinkAutoscalerEvaluator {
-    @Override
-    public String getName() {
-        return "test-custom-evaluator";
-    }
+public class TestCustomEvaluator implements ScalingMetricsEvaluatorPlugin {
 
     @Override
     public Map<ScalingMetric, EvaluatedScalingMetric> evaluateVertexMetrics(
diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
index 1edd695f7..f437ed9c9 100644
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/FlinkOperator.java
@@ -188,8 +188,7 @@ public class FlinkOperator {
         var clusterResourceManager =
                 ClusterResourceManager.of(configManager.getDefaultConfig(), 
client);
         var autoscaler =
-                AutoscalerFactory.create(
-                        client, eventRecorder, clusterResourceManager, 
configManager);
+                AutoscalerFactory.create(client, eventRecorder, 
clusterResourceManager, baseConfig);
         var reconcilerFactory = new ReconcilerFactory(eventRecorder, 
statusRecorder, autoscaler);
         var observerFactory = new 
FlinkDeploymentObserverFactory(eventRecorder);
         var canaryResourceManager = new 
CanaryResourceManager<FlinkDeployment>(configManager);
@@ -213,7 +212,7 @@ public class FlinkOperator {
         var metricManager =
                 MetricManager.createFlinkSessionJobMetricManager(baseConfig, 
metricGroup);
         var statusRecorder = StatusRecorder.create(client, metricManager, 
listeners);
-        var autoscaler = AutoscalerFactory.create(client, eventRecorder, null, 
configManager);
+        var autoscaler = AutoscalerFactory.create(client, eventRecorder, null, 
baseConfig);
         var reconciler = new SessionJobReconciler(eventRecorder, 
statusRecorder, autoscaler);
         var observer = new FlinkSessionJobObserver(eventRecorder);
         var canaryResourceManager = new 
CanaryResourceManager<FlinkSessionJob>(configManager);
diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactory.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactory.java
index d97d33938..506187946 100644
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactory.java
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactory.java
@@ -22,10 +22,11 @@ import org.apache.flink.autoscaler.JobAutoScalerImpl;
 import org.apache.flink.autoscaler.RestApiMetricsCollector;
 import org.apache.flink.autoscaler.ScalingExecutor;
 import org.apache.flink.autoscaler.ScalingMetricEvaluator;
+import org.apache.flink.configuration.Configuration;
 import org.apache.flink.kubernetes.operator.autoscaler.state.ConfigMapStore;
 import 
org.apache.flink.kubernetes.operator.autoscaler.state.KubernetesAutoScalerStateStore;
-import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
 import org.apache.flink.kubernetes.operator.resources.ClusterResourceManager;
+import org.apache.flink.kubernetes.operator.utils.AutoscalerUtils;
 import org.apache.flink.kubernetes.operator.utils.EventRecorder;
 
 import io.fabric8.kubernetes.client.KubernetesClient;
@@ -38,19 +39,18 @@ public class AutoscalerFactory {
             KubernetesClient client,
             EventRecorder eventRecorder,
             ClusterResourceManager clusterResourceManager,
-            FlinkConfigManager configManager) {
+            Configuration conf) {
 
         var stateStore = new KubernetesAutoScalerStateStore(new 
ConfigMapStore(client));
         var eventHandler = new KubernetesAutoScalerEventHandler(eventRecorder);
-        var customEvaluators = 
AutoscalerUtils.discoverCustomEvaluators(configManager);
+        var customEvaluators = AutoscalerUtils.discoverCustomEvaluators(conf);
 
         return new JobAutoScalerImpl<>(
                 new RestApiMetricsCollector<>(),
-                new ScalingMetricEvaluator(),
+                new ScalingMetricEvaluator(customEvaluators),
                 new ScalingExecutor<>(eventHandler, stateStore, 
clusterResourceManager),
                 eventHandler,
                 new KubernetesScalingRealizer(),
-                stateStore,
-                customEvaluators);
+                stateStore);
     }
 }
diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtils.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtils.java
deleted file mode 100644
index 3ea5f077a..000000000
--- 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtils.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.flink.kubernetes.operator.autoscaler;
-
-import org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator;
-import org.apache.flink.configuration.ConfigConstants;
-import org.apache.flink.core.plugin.PluginUtils;
-import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Autoscaler related utility methods for Operator. */
-public class AutoscalerUtils {
-    private static final Logger LOG = 
LoggerFactory.getLogger(AutoscalerUtils.class);
-
-    /**
-     * discovers custom evaluator's for autoscaler.
-     *
-     * @param configManager Flink Config manager
-     * @return A map of discovered custom evaluators, where the key is the 
evaluator name provided
-     *     by {@link FlinkAutoscalerEvaluator#getName()}) and the value is the 
corresponding
-     *     instance.
-     */
-    public static Map<String, FlinkAutoscalerEvaluator> 
discoverCustomEvaluators(
-            FlinkConfigManager configManager) {
-        var conf = configManager.getDefaultConfig();
-        Map<String, FlinkAutoscalerEvaluator> customEvaluators = new 
HashMap<>();
-        PluginUtils.createPluginManagerFromRootFolder(conf)
-                .load(FlinkAutoscalerEvaluator.class)
-                .forEachRemaining(
-                        customEvaluator -> {
-                            String customEvaluatorName = 
customEvaluator.getName();
-                            LOG.info(
-                                    "Discovered custom evaluator for 
autoscaler from plugin directory[{}]: {}.",
-                                    System.getenv()
-                                            .getOrDefault(
-                                                    
ConfigConstants.ENV_FLINK_PLUGINS_DIR,
-                                                    
ConfigConstants.DEFAULT_FLINK_PLUGINS_DIRS),
-                                    customEvaluatorName);
-                            customEvaluator.configure(conf);
-                            if 
(customEvaluators.containsKey(customEvaluatorName)) {
-                                LOG.warn(
-                                        "Duplicate custom evaluator name [{}] 
detected. Overwriting existing [{}] with [{}].",
-                                        customEvaluatorName,
-                                        customEvaluators
-                                                .get(customEvaluatorName)
-                                                .getClass()
-                                                .getName(),
-                                        customEvaluator.getClass().getName());
-                            }
-                            customEvaluators.put(customEvaluatorName, 
customEvaluator);
-                        });
-        return customEvaluators;
-    }
-}
diff --git 
a/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/utils/AutoscalerUtils.java
 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/utils/AutoscalerUtils.java
new file mode 100644
index 000000000..a960994c9
--- /dev/null
+++ 
b/flink-kubernetes-operator/src/main/java/org/apache/flink/kubernetes/operator/utils/AutoscalerUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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.flink.kubernetes.operator.utils;
+
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
+import org.apache.flink.configuration.ConfigConstants;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.core.plugin.PluginUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/** Autoscaler related utility methods for Operator. */
+public class AutoscalerUtils {
+    private static final Logger LOG = 
LoggerFactory.getLogger(AutoscalerUtils.class);
+
+    /**
+     * Discovers custom metric evaluators for autoscaler.
+     *
+     * @param conf Base FlinkConfigManager configuration
+     * @return The list of discovered custom metric evaluators.
+     */
+    public static Collection<ScalingMetricsEvaluatorPlugin> 
discoverCustomEvaluators(
+            Configuration conf) {
+        List<ScalingMetricsEvaluatorPlugin> customEvaluators = new 
ArrayList<>();
+        PluginUtils.createPluginManagerFromRootFolder(conf)
+                .load(ScalingMetricsEvaluatorPlugin.class)
+                .forEachRemaining(
+                        customEvaluator -> {
+                            LOG.info(
+                                    "Discovered custom metric evaluator for 
autoscaler from plugin directory[{}]: {}.",
+                                    System.getenv()
+                                            .getOrDefault(
+                                                    
ConfigConstants.ENV_FLINK_PLUGINS_DIR,
+                                                    
ConfigConstants.DEFAULT_FLINK_PLUGINS_DIRS),
+                                    customEvaluator.getClass().getName());
+                            customEvaluators.add(customEvaluator);
+                        });
+        return customEvaluators;
+    }
+}
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactoryTest.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactoryTest.java
index 6b884044b..28aa942ce 100644
--- 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactoryTest.java
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerFactoryTest.java
@@ -20,7 +20,6 @@ package org.apache.flink.kubernetes.operator.autoscaler;
 import org.apache.flink.autoscaler.JobAutoScaler;
 import org.apache.flink.autoscaler.JobAutoScalerImpl;
 import org.apache.flink.configuration.Configuration;
-import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
 import org.apache.flink.kubernetes.operator.resources.ClusterResourceManager;
 import org.apache.flink.kubernetes.operator.utils.EventRecorder;
 import org.apache.flink.kubernetes.operator.utils.FlinkResourceEventCollector;
@@ -50,7 +49,7 @@ public class AutoscalerFactoryTest {
                                 new FlinkResourceEventCollector(),
                                 new FlinkStateSnapshotEventCollector()),
                         new ClusterResourceManager(Duration.ZERO, 
kubernetesClient),
-                        new FlinkConfigManager(new Configuration()));
+                        new Configuration());
         Assertions.assertTrue(autoScaler instanceof JobAutoScalerImpl);
     }
 }
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtilsTest.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtilsTest.java
index 2d1f365da..b18a31732 100644
--- 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtilsTest.java
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/autoscaler/AutoscalerUtilsTest.java
@@ -18,22 +18,23 @@
 
 package org.apache.flink.kubernetes.operator.autoscaler;
 
+import org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin;
+import org.apache.flink.autoscaler.metrics.TestCustomEvaluator;
 import org.apache.flink.configuration.ConfigConstants;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.kubernetes.operator.TestUtils;
-import org.apache.flink.kubernetes.operator.config.FlinkConfigManager;
+import org.apache.flink.kubernetes.operator.utils.AutoscalerUtils;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.assertj.core.api.Assertions.assertThat;
 
 /** Unit tests for {@link AutoscalerUtils}. */
 public class AutoscalerUtilsTest {
@@ -49,15 +50,12 @@ public class AutoscalerUtilsTest {
                     TestUtils.getTestPluginsRootDir(temporaryFolder));
             TestUtils.setEnv(systemEnv);
 
-            // Discover evaluators
-            var discoveredEvaluators =
-                    AutoscalerUtils.discoverCustomEvaluators(
-                                    new FlinkConfigManager(new 
Configuration()))
-                            .keySet();
-            // Expected evaluators
-            var expectedEvaluators = new 
HashSet<>(List.of("test-custom-evaluator"));
+            Collection<ScalingMetricsEvaluatorPlugin> discoveredEvaluators =
+                    AutoscalerUtils.discoverCustomEvaluators(new 
Configuration());
 
-            assertEquals(expectedEvaluators, discoveredEvaluators);
+            assertThat(discoveredEvaluators)
+                    .hasSize(1)
+                    .hasOnlyElementsOfType(TestCustomEvaluator.class);
         } finally {
             TestUtils.setEnv(originalEnv);
         }
diff --git 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/controller/TestingFlinkDeploymentController.java
 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/controller/TestingFlinkDeploymentController.java
index 165fc697d..5c9f3a41f 100644
--- 
a/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/controller/TestingFlinkDeploymentController.java
+++ 
b/flink-kubernetes-operator/src/test/java/org/apache/flink/kubernetes/operator/controller/TestingFlinkDeploymentController.java
@@ -64,20 +64,20 @@ import java.util.function.BiConsumer;
 public class TestingFlinkDeploymentController
         implements Reconciler<FlinkDeployment>, Cleaner<FlinkDeployment> {
 
-    @Getter private ReconcilerFactory reconcilerFactory;
-    private FlinkDeploymentController flinkDeploymentController;
-    @Getter private StatusUpdateCounter statusUpdateCounter = new 
StatusUpdateCounter();
-    private FlinkResourceEventCollector flinkResourceEventCollector =
+    @Getter private final ReconcilerFactory reconcilerFactory;
+    private final FlinkDeploymentController flinkDeploymentController;
+    @Getter private final StatusUpdateCounter statusUpdateCounter = new 
StatusUpdateCounter();
+    private final FlinkResourceEventCollector flinkResourceEventCollector =
             new FlinkResourceEventCollector();
 
     private EventRecorder eventRecorder;
 
-    @Getter private TestingFlinkResourceContextFactory contextFactory;
+    @Getter private final TestingFlinkResourceContextFactory contextFactory;
+    @Getter private final StatusRecorder<FlinkDeployment, 
FlinkDeploymentStatus> statusRecorder;
+    @Getter private final CanaryResourceManager<FlinkDeployment> 
canaryResourceManager;
 
-    @Getter private StatusRecorder<FlinkDeployment, FlinkDeploymentStatus> 
statusRecorder;
-    @Getter private CanaryResourceManager<FlinkDeployment> 
canaryResourceManager;
-
-    private Map<ResourceID, Tuple2<FlinkDeploymentSpec, Long>> 
currentGenerations = new HashMap<>();
+    private final Map<ResourceID, Tuple2<FlinkDeploymentSpec, Long>> 
currentGenerations =
+            new HashMap<>();
 
     public TestingFlinkDeploymentController(
             FlinkConfigManager configManager, TestingFlinkService 
flinkService) {
@@ -102,7 +102,7 @@ public class TestingFlinkDeploymentController
                                 eventRecorder,
                                 new ClusterResourceManager(
                                         Duration.ZERO, 
flinkService.getKubernetesClient()),
-                                configManager));
+                                new Configuration()));
         canaryResourceManager = new CanaryResourceManager<>(configManager);
         flinkDeploymentController =
                 new FlinkDeploymentController(
diff --git 
a/flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator
 
b/flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin
similarity index 100%
rename from 
flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.FlinkAutoscalerEvaluator
rename to 
flink-kubernetes-operator/src/test/resources/META-INF/services/org.apache.flink.autoscaler.metrics.ScalingMetricsEvaluatorPlugin

Reply via email to