skip to content

Default Conflict Resolution

Gradle's highest-version-wins default, and the dependencyInsight and dependencies tasks that explain the selection. Interviewers ask precisely because it is the opposite of Maven's nearest-wins rule.

on this pageshow

questions

6

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

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

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

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

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