apache/kafka · error · IllegalArgumentException

Circular dependency in sensors: {name} is its own parent.

Error message

Circular dependency in sensors: {name} is its own parent.

What it means

Thrown by the Sensor constructor's checkForest(Set) when the set of parent sensors already contains the sensor being added, i.e. the parent graph is cyclic. Kafka models sensors as a forest (DAG of parents propagated via record()), and a cycle would cause infinite recursion in recordInternal/checkQuotas. The library rejects this at construction with an IllegalArgumentException naming the offending sensor.

Source

Thrown at clients/src/main/java/org/apache/kafka/common/metrics/Sensor.java:159

        super();
        this.registry = registry;
        this.name = Objects.requireNonNull(name);
        this.parents = parents == null ? new Sensor[0] : parents;
        this.metrics = new LinkedHashMap<>();
        this.stats = new ArrayList<>();
        this.config = config;
        this.time = time;
        this.inactiveSensorExpirationTimeMs = TimeUnit.MILLISECONDS.convert(inactiveSensorExpirationTimeSeconds, TimeUnit.SECONDS);
        this.lastRecordTime = time.milliseconds();
        this.recordingLevel = recordingLevel;
        this.metricLock = new Object();
        checkForest(new HashSet<>());
    }

    /* Validate that this sensor doesn't end up referencing itself */
    private void checkForest(Set<Sensor> sensors) {
        if (!sensors.add(this))
            throw new IllegalArgumentException("Circular dependency in sensors: " + name() + " is its own parent.");
        for (Sensor parent : parents)
            parent.checkForest(sensors);
    }

    /**
     * The name this sensor is registered with. This name will be unique among all registered sensors.
     */
    public String name() {
        return this.name;
    }

    List<Sensor> parents() {
        return unmodifiableList(asList(parents));
    }

    /**
     * @return true if the sensor's record level indicates that the metric will be recorded, false otherwise
     */

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Inspect the parents[] array passed to Metrics.sensor(name, config, parents...) and remove any sensor whose ancestor chain already contains the new sensor.
  2. Build the parent graph from leaves to root so a child never references a sensor that has not yet been created, avoiding accidental back-edges.
  3. Maintain a registry of created sensor names and verify the new sensor's name is not present in any candidate parent's parent chain before registering.
  4. If the cycle arises from dynamic wiring, treat the parent relationship as a DAG and deduplicate parents against a visited set before construction.

Example fix

// before
Sensor parent = metrics.sensor("agg");
Sensor child = metrics.sensor("child", new Sensor[]{parent});
// bug: re-registering 'agg' with 'child' as a parent
Sensor bad = metrics.sensor("agg-2", new Sensor[]{child}); // child -> parent -> agg-2 cycle

// after
Sensor parent = metrics.sensor("agg");
Sensor child = metrics.sensor("child", new Sensor[]{parent});
// parents only flow upward; never wire a descendant as ancestor
Defensive patterns

Strategy: validation

Validate before calling

// Before calling metrics.sensor(name, parents...), ensure the parent array is a forest:
Set<Sensor> seen = new HashSet<>();
for (Sensor p : parents) {
    if (!seen.add(p)) {
        throw new IllegalArgumentException("Duplicate parent sensor " + p.name()
            + " — would trip the cycle detector");
    }
}

Type guard

static boolean isAcyclicParents(Sensor[] parents) {
    Set<Sensor> seen = new HashSet<>();
    for (Sensor p : parents) {
        if (!seen.add(p)) return false;
    }
    return true;
}

Prevention

When it happens

Trigger: Creating a Sensor via Metrics.sensor(...) where the parents[] array transitively includes the sensor being created. The simplest case is passing a sensor that is already an ancestor of itself; the recursive checkForest visits each parent and fails when Set.add returns false for the same identity.

Common situations: Code that wires sensors together dynamically (e.g. aggregating per-partition sensors into a parent) and accidentally passes a descendant as a parent of its own ancestor; copy-paste of parent arrays; refactors that reverse the parent/child direction; plugins building derived sensors from sensors fetched out of the Metrics registry.

Related errors


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