apache/maven · error · DIException
Circular references: {}
Error message
Circular references: {} What it means
InjectorImpl.doBind keeps a LinkedHashSet ('current') as a stack of keys currently being registered. If binding a key re-enters doBind for a key already on that stack, it throws DIException 'Circular references:' followed by the full key path, where the repeated key marks the cycle. This guards the registration phase; instance-resolution cycles are reported separately by the resolution stack check.
Source
Thrown at impl/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java:171
Key<?> key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz));
if (clazz.isInterface()) {
bindings.computeIfAbsent(key, $ -> new HashSet<>());
if (key.getQualifier() != null) {
bindings.computeIfAbsent(Key.ofType(clazz), $ -> new HashSet<>());
}
} else if (!Modifier.isAbstract(clazz.getModifiers())) {
Binding<?> binding = ReflectionUtils.generateImplicitBinding(key);
doBind(key, binding);
}
return this;
}
private final LinkedHashSet<Key<?>> current = new LinkedHashSet<>();
private Injector doBind(Key<?> key, Binding<?> binding) {
if (!current.add(key)) {
current.add(key);
throw new DIException("Circular references: " + current);
}
try {
doBindImplicit(key, binding);
Class<?> cls = key.getRawType().getSuperclass();
while (cls != Object.class && cls != null) {
doBindImplicit(Key.of(cls, key.getQualifier()), binding);
if (key.getQualifier() != null) {
bind(Key.ofType(cls), binding);
}
cls = cls.getSuperclass();
}
return this;
} finally {
current.remove(key);
}
}
protected <U> Injector bind(Key<U> key, Binding<U> b) {View on GitHub (pinned to e4093d4e12)
Solutions
- Read the key path in the message: the key that appears twice delimits the cycle; remove one of the two registration sites
- Move all bind* calls to top-level bootstrap code instead of performing them during binding callbacks
- If two modules must contribute bindings for the same key, let them register normally so the binding set handles precedence rather than re-entering doBind
- If no user code re-binds keys, report the framework-internal re-entrancy to maven-di
Example fix
// before: binding A re-enters binding A during registration injector.bindInstance(A.class, makeA(injector)); // makeA() binds A.class again // after: finish construction first, then register once A a = makeA(); injector.bindInstance(A.class, a);
Defensive patterns
Strategy: try-catch
Try / catch
try {
injector.bindInstance(A.class, a);
} catch (DIException e) {
if (e.getMessage().startsWith("Circular references")) {
// message lists the key cycle: remove one of the re-entrant bind sites
throw new ConfigurationException("re-entrant binding detected: " + e.getMessage(), e);
}
throw e;
} Prevention
- Never call injector bind methods from inside binding suppliers, initializers, or callbacks
- Register each key once at top-level bootstrap and avoid re-entrant configuration
When it happens
Trigger: Calling bind/bindInstance/bindSupplier/bindImplicit for key K while K's own binding is still in progress — e.g. binding hooks, implicit-binding generation, or qualifier-aware superclass registration that re-binds the same key re-entrantly.
Common situations: Extension code that performs injector configuration from inside binding callbacks; repeated bootstrap of the same module against one injector; custom Scope or Binding implementations with side effects during registration.
Related errors
- Error while initializing binding {}
- No instance of ${clazz.getName()} is bound to the session sc
- Error in component graph of plugin ${plugin.getId()}: ${e.ge
- Expected type from key {} to be parameterized
- Error while discovering DI classes from classLoader
AI-assisted analysis of apache/maven@e4093d4e12 (2026-08-21).
Data as JSON: /api/errors/e2cd0a864490b074.
Report an issue: GitHub.