apache/maven · error · CycleDetectedException

Edge between '${from}' and '${to}' introduces to cycle in th

Error message

Edge between '${from}' and '${to}' introduces to cycle in the graph

What it means

Graph is the small graph DefaultModelBuilder uses to record model relations (parent/child, relativePath edges) during recursive builds. addEdge() inserts the edge, runs a DFS cycle check, and if the new edge closes a loop it removes the edge and throws CycleDetectedException carrying the cycle path. At the model-builder level this surfaces as the FATAL problem 'Cycle detected between models at X and Y'.

Source

Thrown at impl/maven-impl/src/main/java/org/apache/maven/impl/model/Graph.java:40

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

class Graph {

    final Map<String, Set<String>> graph = new LinkedHashMap<>();

    synchronized void addEdge(String from, String to) throws CycleDetectedException {
        if (graph.computeIfAbsent(from, l -> new HashSet<>()).add(to)) {
            List<String> cycle = visitCycle(graph, Collections.singleton(to), new HashMap<>(), new LinkedList<>());
            if (cycle != null) {
                // remove edge which introduced cycle
                throw new CycleDetectedException(
                        "Edge between '" + from + "' and '" + to + "' introduces to cycle in the graph", cycle);
            }
        }
    }

    private enum DfsState {
        VISITING,
        VISITED
    }

    private static List<String> visitCycle(
            Map<String, Set<String>> graph,
            Collection<String> children,
            Map<String, DfsState> stateMap,
            LinkedList<String> cycle) {
        if (children != null) {
            for (String v : children) {
                DfsState state = stateMap.putIfAbsent(v, DfsState.VISITING);

View on GitHub (pinned to e4093d4e12)

Solutions

  1. Map the parent chain of the two poms named in the message and break the loop: every chain must terminate at a root pom that has no parent
  2. Fix or remove <relativePath> entries so each pom resolves its true parent
  3. Make sure the root/aggregator pom is never also the parent of a pom above it
  4. Run mvn -e to see the full cycle path carried by the exception

Example fix

<!-- before: child/pom.xml has parent = root, but root/pom.xml also claims child as its parent -->
<!-- fix: keep the chain one-directional -->
<parent>
  <groupId>com.acme</groupId>
  <artifactId>root</artifactId>
  <relativePath>../pom.xml</relativePath>
</parent>
<!-- root pom must NOT declare <parent> pointing back at child -->
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate the inheritance graph: walk each pom's parent chain and detect repeats
static Optional<Path> findCycle(Map<Path, Path> parentOf) { // child -> resolved parent pom
    for (Path start : parentOf.keySet()) {
        Set<Path> seen = new HashSet<>();
        for (Path p = start; p != null; p = parentOf.get(p)) {
            if (!seen.add(p)) return Optional.of(p);
        }
    }
    return Optional.empty();
}

Try / catch

try {
    resultGraph.addEdge(from, to);
} catch (Graph.CycleDetectedException e) {
    // e.getCycle() lists the loop; report it and stop instead of retrying
}

Prevention

When it happens

Trigger: Pom A declares pom B as its parent while B (or something in B's parent chain) declares A as its parent; <relativePath> values that make the inheritance graph loop back on itself.

Common situations: Hand-edited multi-module projects where parent/child relations were swapped; an aggregator accidentally declared as parent of its own parent; refactoring that moved parent poms but left stale relativePath values pointing at children.

Related errors


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