gradle/gradle · error · InvalidUserDataException
Cannot declare module replacement %s->%s because it introduc
Error message
Cannot declare module replacement %s->%s because it introduces a cycle: %s
What it means
Whenever a new module replacement module(a){ replacedBy(b) } is declared, detectCycles() walks the existing replacement chain starting from the new target. If the walk ever revisits a module already on the path, resolution would loop forever, so Gradle throws InvalidUserDataException and prints the exact cycle (source->target->...->source).
Source
Thrown at platforms/software/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/ComponentModuleMetadataContainer.java:96
private static void detectCycles(Map<ModuleIdentifier, ImmutableModuleReplacements.Replacement> replacements, ModuleIdentifier source, ModuleIdentifier target) {
if (source.equals(target)) {
throw new InvalidUserDataException(String.format("Cannot declare module replacement that replaces self: %s->%s", source, target));
}
ModuleIdentifier m = unwrap(replacements.get(target));
if (m == null) {
//target does not exist in the map, there's no cycle for sure
return;
}
Set<ModuleIdentifier> visited = new LinkedHashSet<>();
visited.add(source);
visited.add(target);
while(m != null) {
if (!visited.add(m)) {
//module was already visited, there is a cycle
throw new InvalidUserDataException(
format("Cannot declare module replacement %s->%s because it introduces a cycle: %s",
source, target, Joiner.on("->").join(visited) + "->" + source));
}
m = unwrap(replacements.get(m));
}
}
private static ModuleIdentifier unwrap(ImmutableModuleReplacements.Replacement replacement) {
return replacement == null ? null : replacement.getTarget();
}
private static NotationParser<Object, ModuleIdentifier> parser(ImmutableModuleIdentifierFactory moduleIdentifierFactory) {
return NotationParserBuilder
.toType(ModuleIdentifier.class)
.fromCharSequence(new ModuleIdentifierNotationConverter(moduleIdentifierFactory))
.toComposite();
}
}View on GitHub (pinned to 534f27719b)
Solutions
- Pick one canonical module and make every other module point to it (a chain, never a ring) — e.g. keep g:a->g:b and change or delete g:b->g:a.
- Audit all components.modules / componentModule declarations across every applied script and plugin and remove the redundant reverse mapping that closes the loop.
- Rerun the build: the message includes the full cycle path (source->...->source), which names exactly which declarations to fix.
Example fix
// before
components {
modules {
module("com.old:lib") { replacedBy("com.new:lib") }
module("com.new:lib") { replacedBy("com.old:lib") } // closes the cycle
}
}
// after
components {
modules {
module("com.old:lib") { replacedBy("com.new:lib") } // single direction only
}
} Defensive patterns
Strategy: validation
Validate before calling
def declareReplacements(Map<String, String> replacements) {
def seen = new HashSet<String>()
replacements.each { src, tgt ->
def cur = tgt
while (cur != null) {
assert cur != src : "Replacement cycle detected involving $src"
cur = replacements[cur]
}
}
components.modules { replacements.each { s, t -> module(s).replacedBy(t) } }
} Prevention
- Keep the replacement map acyclic by design: all arrows point toward one canonical module.
- Centralize module replacements in a single script/plugin instead of scattering them across build files so cycles are visible at a glance.
When it happens
Trigger: module("g:a"){ replacedBy("g:b") } combined with module("g:b"){ replacedBy("g:a") }; longer rings such as g:a->g:b->g:c->g:a; any replacement whose target transitively resolves back to the source.
Common situations: Replacement declarations spread across multiple build scripts or plugins that individually look fine but together close a ring; two teams aliasing modules toward each other during a migration/merge; incrementally adding reverse-mappings after a rename.
Related errors
- Cannot remove '%s' from '%s' as it is a filtered collection
- Cannot remove element from '%s' as it is a filtered collecti
- The version of 'scala-library' was changed while using the d
- Cannot infer Scala class path because no Scala library Jar w
- Failed to load JUnit 4. Please ensure that the JUnit 4 libr
AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22).
Data as JSON: /api/errors/56c3d82874d1fe6e.
Report an issue: GitHub.