apache/kafka · error · IllegalArgumentException

Metric {metricName} already exists

Error message

Metric {metricName} already exists

What it means

Thrown by PluginMetricsImpl.addMetric when the given MetricName is already in the instance's metricNames set. PluginMetrics tracks each metric it owns so it can clean them up on close; registering the same MetricName twice is a programmer error. IllegalArgumentException surfaces the duplicate before the underlying Metrics.addMetric would also reject it.

Source

Thrown at clients/src/main/java/org/apache/kafka/common/metrics/internals/PluginMetricsImpl.java:64

    @Override
    public MetricName metricName(String name, String description, LinkedHashMap<String, String> tags) {
        if (closing) throw new IllegalStateException("This PluginMetrics instance is closed");
        for (String tagName : tags.keySet()) {
            if (this.tags.containsKey(tagName)) {
                throw new IllegalArgumentException("Cannot use " + tagName + " as a tag name");
            }
        }
        Map<String, String> metricsTags = new LinkedHashMap<>(this.tags);
        metricsTags.putAll(tags);
        return metrics.metricName(name, GROUP, description, metricsTags);
    }

    @Override
    public void addMetric(MetricName metricName, MetricValueProvider<?> metricValueProvider) {
        if (closing) throw new IllegalStateException("This PluginMetrics instance is closed");
        if (metricNames.contains(metricName)) {
            throw new IllegalArgumentException("Metric " + metricName + " already exists");
        }
        metrics.addMetric(metricName, metricValueProvider);
        metricNames.add(metricName);
    }

    @Override
    public void removeMetric(MetricName metricName) {
        if (closing) throw new IllegalStateException("This PluginMetrics instance is closed");
        if (metricNames.contains(metricName)) {
            metrics.removeMetric(metricName);
            metricNames.remove(metricName);
        } else {
            throw new IllegalArgumentException("Unknown metric " + metricName);
        }
    }

    @Override
    public Sensor addSensor(String name) {

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Guard with metricNames.contains(metricName) (or a plugin-side Set) before calling addMetric, or use addMetric only once per logical metric.
  2. Add a distinguishing tag (task-id, partition, client-id) so each logical source has a unique MetricName.
  3. Call pluginMetrics.removeMetric(metricName) before re-adding, or restart the plugin's PluginMetrics instance to reset the set.
  4. Move addMetric to a single initialization point so it cannot be hit multiple times per lifecycle.

Example fix

// before
pluginMetrics.addMetric(name, provider); // first task
pluginMetrics.addMetric(name, provider); // second task -> throws

// after
MetricName perTask = pluginMetrics.metricName("rx", "d",
    new LinkedHashMap<>(Map.of("task-id", String.valueOf(taskId))));
pluginMetrics.addMetric(perTask, provider);
Defensive patterns

Strategy: try-catch

Try / catch

try {
    pluginMetrics.addMetric(metricName, provider);
} catch (IllegalArgumentException e) {
    // metric already registered (e.g. on re-init or duplicate setup); treat as idempotent
    log.debug("Metric {} already registered, skipping", metricName, e);
}

Prevention

When it happens

Trigger: Calling pluginMetrics.addMetric(metricName, provider) twice with an equal MetricName (same name, group="plugins", and tags). Equality is MetricName equality, so identical name+tags-grouped identity triggers the exception.

Common situations: Plugin init code that runs twice (e.g. idempotent re-init not guarded); per-task metric registration without a task-id tag so every task collides on the same MetricName; tests that reuse a PluginMetrics and forget to removeMetric between cases; refactors that moved addMetric into a loop without a contains check.

Related errors


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/dbf5ffd0d81950c6.json. Report an issue: GitHub.