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
- Inspect the parents[] array passed to Metrics.sensor(name, config, parents...) and remove any sensor whose ancestor chain already contains the new sensor.
- 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.
- 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.
- 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
- Never wire a sensor (directly or transitively) back into its own parent chain.
- Dedupe the parents array before registering — duplicate ancestors trip the forest check.
- Build parent sensors before children and keep the parent graph a strict tree/forest.
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
- Telemetry is not enabled. Set config `enable.metrics.push` t
- Did not recognize recording level {configId}
- A metric named '{metricName}' already exists, can't register
- The maximum value {max} must be greater than the minimum val
- Must be at least 1 bucket
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/147bad3529dafffa.json.
Report an issue: GitHub.