skip to content

Resolution and Conflicts

What Gradle does when two paths demand different versions: default conflict resolution, forcing and constraints, rewriting rules, substitution, capabilities, and locking. This is the area that separates Gradle users from Gradle debuggers.

on this pageshow

explore

questions

page 1 of 2

When two transitive dependencies require different versions of the same module, what does Gradle do by default?

level: juniorimportance: must knowfreq 78%

answer

  1. one version per module per configuration
  2. highest requested version wins
  3. module identity = group:name
  4. dependencies / dependencyInsight to diagnose
  5. version ordering, not string compare

basics

~10 s

By default Gradle picks the highest requested version of the module and uses that single version everywhere on the classpath. This is 'highest-version-wins' conflict resolution.

solid answer

~40 s

When the dependency graph requests several versions of the same module (same group:name), Gradle's default conflict resolution selects the **highest** version among all the requested versions and aligns the whole graph to it — one version per module per configuration. 'Highest' is determined by Gradle's version-ordering rules, not plain string compare (it understands `1.10` > `1.9`, qualifiers like `-rc`, `-SNAPSHOT`, etc.). This is intentional: a single classpath cannot have two copies of the same module, and the newer version is assumed backwards-compatible. The losing version is evicted. You can see the winner with `./gradlew dependencies` (it annotates the chosen version) or, for a specific module, `./gradlew dependencyInsight --dependency <name>`, which shows exactly why a version was selected.

code

bash · 6 lines
bash
# See the whole resolved graph for one configuration
./gradlew dependencies --configuration runtimeClasspath

# Focus on one module and learn WHY a version was chosen
./gradlew dependencyInsight --configuration runtimeClasspath --dependency guava
# -> guava:30.0-jre -> 31.0-jre  (by conflict resolution: between versions ...)

go deeper

for a junior

Know the headline: same module requested at different versions -> Gradle keeps the highest, one version per classpath.

for a middle

Explain module identity = group:name, per-configuration resolution, and that 'highest' uses version ordering not string compare; name the diagnostic tasks.

for a senior

Contrast with Maven's nearest-wins, explain that it's a graph-level metadata decision before download, and discuss when the heuristic is wrong (major bumps).

for a principal

Frame highest-wins as the org-wide default policy, its risk surface for breaking changes, and when to escalate to failOnVersionConflict + governed overrides.

## The problem A Java/Kotlin runtime classpath can hold only **one** version of a given class. But real dependency graphs are messy: library A pulls in `guava:31.0`, library B pulls in `guava:30.0`. Both can't be present, so Gradle must pick one. The rule that decides which is the **conflict resolution strategy**. ## Gradle's default: highest-version-wins Gradle's default strategy is `latest` / highest-version-wins. Among every version *requested* for a module (`group:name`), Gradle selects the **highest** and uses it for the entire build's view of that module within a configuration. The other requests are *evicted*; their declaring dependencies now resolve against the winner. Key points: - 'Module' identity is `group:name` (the version is excluded from identity). Two artifacts with the same group:name are the *same module* and therefore conflict. - 'Highest' uses Gradle's **version ordering**, not lexicographic string compare. It splits on `.`, `-`, `_`, `+`, compares numeric parts numerically (`1.10 > 1.9`), and applies special handling to qualifiers (`dev < rc < release`, `-SNAPSHOT` is special). - Resolution happens **per configuration** (e.g. `compileClasspath`, `runtimeClasspath`) and per project — each resolves independently. - This is a *graph* decision, made before artifacts are downloaded, using metadata (POM/Gradle Module Metadata). ## Diagnosing what happened Two built-in tasks: - `./gradlew dependencies` — prints the full resolved tree per configuration. A line like `guava:30.0 -> 31.0` means 30.0 was requested but resolved to 31.0 (it lost the conflict). `(*)` marks a subtree already shown. - `./gradlew dependencyInsight --configuration runtimeClasspath --dependency guava` — focuses on **one** module and prints the *reasons* for the selected version (e.g. 'by conflict resolution: between versions 30.0 and 31.0'). ## Why default to highest? Under semantic versioning, a higher minor/patch is meant to be backwards-compatible, so picking the newest is the safest automatic choice. It is a *heuristic*, not a guarantee — a major-version bump can break callers, which is why you sometimes need to override (forcing, constraints, etc. — separate topics). ```kotlin dependencies { implementation("com.google.guava:guava:30.0-jre") // direct implementation("some.lib:foo:1.0") // foo pulls guava:31.0-jre transitively } // runtimeClasspath resolves guava -> 31.0-jre (highest wins) ``` Run `./gradlew dependencyInsight --dependency guava` and you'll see the selected `31.0-jre` with reason 'by conflict resolution'.

  • Is 'highest' decided by string comparison?
    No. Gradle uses version-ordering rules: it splits on separators and compares numeric parts numerically, so 1.10 > 1.9, and handles qualifiers (rc, SNAPSHOT) specially rather than alphabetically.
  • Does the conflict get resolved once for the whole build?
    No — resolution is per configuration (and per project). compileClasspath and runtimeClasspath each resolve independently, though they usually land on the same version.

Like a meeting room that fits only one projector: if two people bring projectors, you use the newer model and the old one sits out — everyone shares the one that's plugged in.

saying these in an interview costs you the question

  • Saying Gradle keeps both versions on the classpath — it cannot; one is evicted.
  • Claiming highest-wins compares versions as plain strings.
  • Confusing Gradle's default (highest) with Maven's 'nearest definition wins'.

context

open as a page

What does `resolutionStrategy.force('com.google.guava:guava:32.1.3-jre')` do, and when would you reach for it?

level: juniorimportance: must knowfreq 58%

basics

~10 s

It pins Guava to exactly that version for the whole configuration, overriding whatever version conflict resolution would otherwise pick — even if another dependency wants a higher one.

open as a page

What is dependency locking in Gradle, and why would you enable it?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Dependency locking pins the exact versions a configuration resolves to, saving them in a lockfile. With dynamic versions or version ranges, builds can drift over time; locking makes resolution reproducible across machines and over time.

open as a page

What is dependency substitution in Gradle, and what problem does it solve?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Dependency substitution lets you swap one dependency for another during resolution — for example, replacing a published external module with a local project so you build and test against your own source instead of a release.

open as a page

What is a 'capability' in Gradle dependency management, and what problem does it solve compared to plain version conflict resolution?

level: middleimportance: must knowfreq 45%

basics

~20 s

A capability is a label (group:name:version) saying 'I provide this feature'. By default a module provides one capability matching its GAV. Two modules offering the same capability conflict, even if they are different modules — like log4j and log4j-over-slf4j both providing logging.

open as a page

How do you use the dependencyInsight task to find out why Gradle selected a particular version of a module?

level: middleimportance: must knowfreq 62%

basics

~10 s

Run ./gradlew dependencyInsight --configuration <cfg> --dependency <module>. It prints the selected version, every path that requested the module, and the reason for selection — e.g. 'by conflict resolution'.

open as a page

How do you pin a transitive dependency's version using a `strictly` constraint, and how does it differ from `force`?

level: middleimportance: must knowfreq 52%

basics

~20 s

Add a constraint with a strict version: constraints { implementation('g:a') { version { strictly('1.2.3') } } }. Unlike force, it's a first-class version requirement that participates in resolution and fails loudly if something genuinely needs another version.

open as a page

How do you enable dependency locking and generate the lockfile in a Gradle build?

level: middleimportance: must knowfreq 50%

basics

~10 s

Add a dependencyLocking { lockAllConfigurations() } block, then run a resolving task with --write-locks (e.g. ./gradlew dependencies --write-locks). Gradle writes the resolved versions to gradle.lockfile, which you commit.

open as a page

Walk through the classic google-collections to guava problem and how a module replacement rule fixes it.

level: middleimportance: must knowfreq 38%

basics

~10 s

Guava absorbed all of google-collections' classes. If both end up on the classpath you get duplicate classes and NoSuchMethodError. Declaring google-collections replacedBy guava makes Gradle evict the old one, leaving only guava.

open as a page

What is module replacement in Gradle, and what problem does it solve?

level: middleimportance: must knowfreq 40%

basics

~20 s

Module replacement tells Gradle that one module is a drop-in successor of another (e.g. google-collections was replaced by guava), so during conflict resolution Gradle treats them as the same logical component and keeps only the replacement.

open as a page

What is the ResolutionResult API in Gradle, and how does it differ from simply listing the files of a configuration?

level: middleimportance: must knowfreq 55%

basics

~10 s

ResolutionResult gives the resolved dependency graph as components and dependencies, including versions chosen and reasons. Listing files gives only the resolved artifacts on disk, with no graph structure or selection metadata.

open as a page

Compare details.useVersion() and details.useTarget() inside an eachDependency rule. When would you reach for each?

level: middleimportance: must knowfreq 45%

basics

~10 s

useVersion changes only the version of the requested module, keeping its group and name. useTarget replaces the whole coordinate (group:name:version), letting you redirect one module to an entirely different one.

open as a page

What does resolutionStrategy.eachDependency do, and when in the build does the closure you pass to it run?

level: middleimportance: must knowfreq 55%

basics

~10 s

eachDependency registers a rule that Gradle invokes for every dependency while resolving a configuration. The rule receives DependencyResolveDetails and can rewrite the requested version or coordinates before the dependency graph is built.

open as a page

How does a composite build with includeBuild use dependency substitution, and when would you reach for it?

level: middleimportance: must knowfreq 50%

basics

~10 s

includeBuild wires a separate Gradle build into yours. Gradle automatically substitutes any external dependency whose coordinates match a project published by the included build, so you transparently build against that build's source.

open as a page

How do you resolve a capability conflict programmatically in Gradle, picking one variant over another?

level: seniorimportance: must knowfreq 40%

basics

~10 s

Use resolutionStrategy.capabilitiesResolution.withCapability('group:name') { select(...) } inside a configuration. The closure receives the conflicting candidates and you call select() to pick one, or selectHighestVersion() to take the newest.

open as a page

What does the dependencies task show, and how do you read its output to spot an evicted (downgraded or upgraded) version?

level: juniorimportance: should knowfreq 50%

basics

~10 s

./gradlew dependencies prints the full resolved dependency tree per configuration. A line like 'lib:1.0 -> 2.0' shows that 1.0 was requested but the build resolved 2.0 — that request lost the conflict.

open as a page

A vulnerable transitive dependency keeps slipping into your build via several libraries. How would you use eachDependency to pin it to a safe version across the whole graph?

level: juniorimportance: should knowfreq 40%

basics

~10 s

Add configurations.all { resolutionStrategy.eachDependency { if it's the vulnerable module, useVersion("safe") } }. The rule fires for every transitive, so every path to that module resolves to the safe version.

open as a page

Your Gradle build fails with 'Cannot select module ... Multiple variants ... provide the same capability'. What does this mean and what are your options?

level: middleimportance: should knowfreq 38%

basics

~20 s

Two variants in your graph declare the same capability, so they're mutually exclusive and Gradle refuses to put both on the classpath. You must pick one — via a capabilitiesResolution rule, or by removing/substituting one of the modules.

open as a page

What does failOnVersionConflict() do, and why might a team enable it?

level: middleimportance: should knowfreq 55%

basics

~10 s

It switches a configuration's resolution strategy so that any version conflict between transitive dependencies makes the build fail instead of silently picking the highest version, forcing the team to resolve it explicitly.

open as a page

Explain the difference between `require`, `prefer`, and `strictly` inside a `version { }` block, and how `reject` interacts with them.

level: middleimportance: should knowfreq 40%

basics

~20 s

require is the minimum acceptable (default); higher is fine. prefer is a soft tie-breaker used only when nothing else decides. strictly is a hard requirement that can downgrade and fail. reject blacklists versions that otherwise qualify.

open as a page

What is the difference between `--write-locks` and `--update-locks`, and when do you use each?

level: middleimportance: should knowfreq 42%

basics

~10 s

--write-locks re-resolves freely and rewrites all lock entries for the configurations it resolves. --update-locks group:module re-resolves but only lets the named modules change, keeping every other locked version pinned.

open as a page

Where and how do you declare a module replacement rule, and how can you apply it consistently across many subprojects?

level: middleimportance: should knowfreq 25%

basics

~20 s

Declare it in the dependencies block: dependencies { modules { module('g:old') { replacedBy('g:new', 'reason') } } }. To apply across subprojects, put it in a convention plugin or a shared script applied to each project.

open as a page

What is an artifactView in Gradle, and why would you set lenient = true on it?

level: middleimportance: should knowfreq 45%

basics

~10 s

An artifactView lazily produces a filtered/transformed set of resolved artifacts from a configuration. lenient = true makes it ignore resolution and artifact failures instead of throwing, returning whatever resolved successfully.

open as a page

Can dependency substitution change a dependency's version or swap it for a different external module? Show how and explain the resolution implications.

level: middleimportance: should knowfreq 26%

basics

~10 s

Yes. substitute(module("g:n")).using(module("g:n:1.5")) changes the version; substitute(module("g:n")).using(module("g2:n2:1.0")) swaps to a different module. The substituted node then participates in normal conflict resolution.

open as a page

How does dependency substitution differ from module replacement, exclusion, and forcing — and when is each appropriate?

level: middleimportance: should knowfreq 33%

basics

~20 s

Substitution redirects a dependency to a different one (including a local project) during resolution. Replacement signals two modules are the same library under different coordinates. Exclude removes a transitive. Force/pin only chooses a version. Pick by intent.

open as a page

How do you make two third-party modules that don't declare any shared capability become mutually exclusive in Gradle?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Add a component metadata rule that attaches the same capability to both modules: components.withModule(...) { allVariants { withCapabilities { addCapability(group, name, version) } } }. Once both share a capability, Gradle treats them as conflicting.

open as a page

Why can the same module resolve to different versions in different configurations, and how does that relate to default conflict resolution?

level: seniorimportance: should knowfreq 38%

basics

~10 s

Each resolvable configuration (compileClasspath, runtimeClasspath, etc.) is resolved independently, so highest-version-wins runs separately per configuration. Different requested-version sets can therefore yield different winners.

open as a page

How does Gradle's default conflict resolution differ from Maven's, and why does it matter?

level: seniorimportance: should knowfreq 48%

basics

~10 s

Maven uses 'nearest definition wins' — the version closest to the root of the tree. Gradle uses 'highest version wins' regardless of depth. So migrating builds can silently pick different versions.

open as a page

A teammate forced a transitive library to an older version and now a runtime NoSuchMethodError appears. How do you diagnose what won resolution, and why might `force`/`strictly` be the wrong fix here?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Run gradle dependencyInsight --dependency lib to see the selected version and reason. A forced downgrade can pull in an API too old for callers, causing NoSuchMethodError; the fix is usually to align upward or remove the override, not pin lower.

open as a page

What does a Gradle lockfile look like, and what happens when resolution no longer matches it?

level: seniorimportance: should knowfreq 35%

basics

~10 s

gradle.lockfile lists group:artifact:version=configurations lines plus an empty= line. On a normal build, if resolution would differ from these pins, Gradle fails with a lock-state mismatch error telling you what changed.

open as a page

showing 1–30 of 41