apache/maven · error · RepositoryException

Artifact relocations form a cycle: {visited}

Error message

Artifact relocations form a cycle: {visited}

What it means

DefaultArtifactDescriptorReader follows <relocation> entries in POMs, guarded by a visited set of groupId:artifactId:baseVersion. Revisiting an entry, i.e. artifact A relocates to B which relocates back to A, aborts with RepositoryException 'Artifact relocations form a cycle' listing the chain; the build then fails with ArtifactDescriptorException unless the artifact-descriptor policy ignores invalid descriptors (in which case the descriptor resolves to null).

Source

Thrown at compat/maven-resolver-provider/src/main/java/org/apache/maven/repository/internal/DefaultArtifactDescriptorReader.java:167

                versionRequest.setTrace(trace);
                VersionResult versionResult = versionResolver.resolveVersion(session, versionRequest);

                a = a.setVersion(versionResult.getVersion());

                versionRequest =
                        new VersionRequest(pomArtifact, request.getRepositories(), request.getRequestContext());
                versionRequest.setTrace(trace);
                versionResult = versionResolver.resolveVersion(session, versionRequest);

                pomArtifact = pomArtifact.setVersion(versionResult.getVersion());
            } catch (VersionResolutionException e) {
                result.addException(e);
                throw new ArtifactDescriptorException(result);
            }

            if (!visited.add(a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getBaseVersion())) {
                RepositoryException exception =
                        new RepositoryException("Artifact relocations form a cycle: " + visited);
                invalidDescriptor(session, trace, a, exception);
                if ((getPolicy(session, a, request) & ArtifactDescriptorPolicy.IGNORE_INVALID) != 0) {
                    return null;
                }
                result.addException(exception);
                throw new ArtifactDescriptorException(result);
            }

            ArtifactResult resolveResult;
            try {
                ArtifactRequest resolveRequest =
                        new ArtifactRequest(pomArtifact, request.getRepositories(), request.getRequestContext());
                resolveRequest.setTrace(trace);
                resolveResult = artifactResolver.resolveArtifact(session, resolveRequest);
                pomArtifact = resolveResult.getArtifact();
                result.setRepository(resolveResult.getRepository());
            } catch (ArtifactResolutionException e) {
                if (e.getCause() instanceof ArtifactNotFoundException artifactNotFoundException) {

View on GitHub (pinned to e4093d4e12)

Solutions

  1. Inspect the chain printed in the message and fix the looping <relocation> in the offending POM(s): each relocation must move to a strictly new coordinate
  2. If you do not own those POMs, pin the dependency to a version whose relocation is well-formed, or exclude/replace the artifact with its relocation target
  3. Repository managers: audit rewrite rules for mutual relocations
  4. As a stopgap, a lenient artifact-descriptor policy (IGNORE_INVALID) makes the reader skip the broken descriptor instead of failing the build

Example fix

<!-- before: old-lib POM relocates to new-lib, and new-lib POM relocates
     back to old-lib -> cycle: old-lib -> new-lib -> old-lib -->
<project>
  <groupId>com.example</groupId>
  <artifactId>old-lib</artifactId><version>1.9</version>
  <distributionManagement>
    <relocation>
      <groupId>com.example</groupId><artifactId>new-lib</artifactId>
    </relocation>
  </distributionManagement>
</project>

<!-- after: relocate only forward, never point back -->
<project>
  <groupId>com.example</groupId>
  <artifactId>old-lib</artifactId><version>1.9</version>
  <distributionManagement>
    <relocation>
      <groupId>com.example</groupId><artifactId>new-lib</artifactId>
    </relocation>
  </distributionManagement>
</project>
<!-- and new-lib's POM contains NO relocation back to old-lib -->
Defensive patterns

Strategy: try-catch

Try / catch

try {
    descriptor = descriptorReader.readArtifactDescriptor(session, request);
} catch (ArtifactDescriptorException e) {
    boolean relocationCycle = e.getExceptions().stream()
            .anyMatch(t -> t.getMessage() != null
                    && t.getMessage().contains("relocations form a cycle"));
    if (relocationCycle) {
        // pin the relocation target explicitly instead of following the loop
        dependencies = replaceWithRelocationTarget(dependencies);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Resolving an artifact whose relocation chain returns to an already-visited coordinate:baseVersion, mutual relocations between two artifacts, or a POM relocating to its own coordinates at a version that normalizes to the same base version.

Common situations: Coordinate renames where both sides publish relocations pointing at each other; repository-manager rewrite rules that inject looping relocations; publishing automation that copies relocation blocks between related POMs.

Related errors


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