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} &lt; {@link String} &gt; 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

  1. Run mvn dependency:tree -Dverbose (or dependency:analyze) to locate the cycle using the artifact reported by the exception
  2. Break the cycle in your modules: remove one edge, or refactor shared code into a module both depend on
  3. If the cycle arrives via a published third-party POM, exclude the transitive dependency that closes the cycle
  4. 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

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


AI-assisted analysis of apache/maven@e4093d4e12 (2026-08-21). Data as JSON: /api/errors/9c4d6c822bd3bd4b. Report an issue: GitHub.