apache/maven · error · CyclicDependencyException
A dependency has introduced a cycle
Error message
A dependency has introduced a cycle
What it means
ResolutionNode.addDependencies() builds the legacy dependency graph; when a candidate dependency's conflict id is already among the current node's parents, adding it would make the artifact its own ancestor, so Maven throws CyclicDependencyException. The offending artifact is attached to the exception and its dependency trail is set beforehand, so the trail plus message identify the exact closing edge of the cycle.
Source
Thrown at compat/maven-compat/src/main/java/org/apache/maven/artifact/resolver/ResolutionNode.java:91
public Artifact getArtifact() {
return artifact;
}
public Object getKey() {
return artifact.getDependencyConflictId();
}
public void addDependencies(
Set<Artifact> artifacts, List<ArtifactRepository> remoteRepositories, ArtifactFilter filter)
throws CyclicDependencyException, OverConstrainedVersionException {
if (artifacts != null && !artifacts.isEmpty()) {
children = new ArrayList<>(artifacts.size());
for (Artifact a : artifacts) {
if (parents.contains(a.getDependencyConflictId())) {
a.setDependencyTrail(getDependencyTrail());
throw new CyclicDependencyException("A dependency has introduced a cycle", a);
}
children.add(new ResolutionNode(a, remoteRepositories, this));
}
children = Collections.unmodifiableList(children);
} else {
children = Collections.emptyList();
}
trail = null;
}
/**
* @return {@link List} < {@link String} > with artifact ids
* @throws OverConstrainedVersionException if version specification is over constrained
*/
public List<String> getDependencyTrail() throws OverConstrainedVersionException {
List<Artifact> trial = getTrail();
View on GitHub (pinned to e4093d4e12)
Solutions
- Run mvn dependency:tree -Dverbose (or dependency:analyze) to locate the cycle using the artifact reported by the exception
- Break the cycle in your modules: remove one edge, or refactor shared code into a module both depend on
- If the cycle arrives via a published third-party POM, exclude the transitive dependency that closes the cycle
- Re-run the build to confirm the graph is acyclic
Example fix
<!-- before: module 'impl' pulls 'api' transitively, closing a cycle -->
<dependency>
<groupId>com.example</groupId>
<artifactId>shared-lib</artifactId>
</dependency>
<!-- after: exclude the edge that closes the cycle -->
<dependency>
<groupId>com.example</groupId>
<artifactId>shared-lib</artifactId>
<exclusions>
<exclusion>
<groupId>com.example</groupId>
<artifactId>api</artifactId>
</exclusion>
</exclusions>
</dependency> Defensive patterns
Strategy: try-catch
Try / catch
Catch CyclicDependencyException; read getArtifact() and its dependency trail to identify the closing edge, then fail the build with the cycle path printed - do not retry, the graph is structurally invalid.
Prevention
- Keep module dependency directions one-way (impl -> api, never both); enforce in reviews.
- Run mvn dependency:tree -Dverbose in CI to surface cycles before they hit the resolver.
- Be cautious with test-jar type dependencies; they are a common source of module cycles.
When it happens
Trigger: Resolving a dependency graph (through the legacy resolver in maven-compat, e.g. old plugins or DefaultArtifactCollector) where project A depends on B and B, transitively, depends back on A.
Common situations: Sibling modules referencing each other (api depends on impl while impl depends on api); test-jar dependencies creating module cycles; third-party libraries accidentally published with cyclic POMs.
Related errors
- Invalid version for dependency " + dependency.getManagementK
- Unable to download the artifact from any repository
- System artifact: {} has no file attached
- Unable to get a selected Version for {}
- Failed to process POM for " + artifact.getId() + ": " + miss
AI-assisted analysis of apache/maven@e4093d4e12 (2026-08-21).
Data as JSON: /api/errors/9c4d6c822bd3bd4b.
Report an issue: GitHub.