skip to content

When two paths in the dependency graph demand different versions of the same artifact, how does Maven decide which version to use?

level: middleimportance: must knowfreq 85%

answer

  1. nearest definition wins = shortest path
  2. tie -> first declared in POM
  3. NOT highest version
  4. depth 1 (direct) always wins
  5. dependencyManagement overrides depth

basics

~10 s

Maven uses nearest-wins: the version closest to your project in the dependency tree (fewest hops) is chosen. If two are at the same depth, the one declared first in the POM wins.

solid answer

~50 s

Maven resolves version conflicts by **dependency mediation**, whose rule is **nearest definition wins**: among all versions of the same groupId:artifactId in the graph, the one at the shallowest depth (fewest edges from the root project) is selected. When two candidates sit at equal depth, Maven breaks the tie by **declaration order** — the first one encountered in the POM (top-down) wins. Crucially, Maven does NOT pick the highest version, and it does not merge or range-resolve by default. This means a deep transitive bump can be silently overridden by a nearer, older version, sometimes causing NoSuchMethodError at runtime. To take control you either declare the desired version directly (depth 1 always wins), or — the recommended approach — pin it in `<dependencyManagement>`, which forces a version regardless of graph position. `mvn dependency:tree -Dverbose` shows which versions were omitted for conflict.

code

bash · 4 lines
bash
mvn dependency:tree -Dverbose
# [INFO] +- A:1.0:compile
# [INFO] |  \- (C:2.0:compile - omitted for conflict with 1.0)
# [INFO] \- C:1.0:compile        <-- nearest wins

go deeper

for a junior

Know the phrase 'nearest-wins' and that direct dependencies override transitive ones.

for a middle

Explain depth measurement, the first-declared tie-break, and that highest version is NOT the rule.

for a senior

Diagnose runtime NoSuchMethodError from mediation and fix via dependencyManagement; contrast with Gradle highest-wins.

for a principal

Mandate convergence enforcement and BOM-driven version control org-wide so mediation surprises never reach production.

## The problem The transitive graph can reach the **same** artifact (same `groupId:artifactId`) through different paths, each declaring a **different version**. Only ONE version can be on the classpath. **Mediation** is how Maven decides. ## Rule 1: nearest-wins (shortest path) Maven measures each occurrence's **depth** = number of edges from your project (the root) to that node. ``` your-app +- A 1.0 | \- C 2.0 (depth 2) \- C 1.0 (depth 1) <-- WINS ``` Even though `C 2.0` is newer, `C 1.0` is nearer (depth 1 vs depth 2), so Maven uses `C 1.0`. **Newer does not win — nearer wins.** ## Rule 2: first-declared tie-breaker If two occurrences are at the **same depth**, Maven keeps the one it encountered **first** while reading the POM top to bottom. ``` your-app +- A 1.0 | \- C 2.0 (depth 2, seen first) <-- WINS \- B 1.0 \- C 1.5 (depth 2, seen later) ``` Reorder `A` and `B` in your POM and the winner flips — a classic surprise. ## What Maven does NOT do - It does not choose the highest version. - It does not error on conflict by default (you can opt in via the maven-enforcer-plugin `dependencyConvergence` rule). ## Taking control 1. **Declare it directly** in your POM → depth 1, always wins. 2. **`<dependencyManagement>`** → forces a version for any matching artifact ANYWHERE in the graph, independent of depth. Preferred for governance. ```xml <dependencyManagement> <dependencies> <dependency> <groupId>com.example</groupId> <artifactId>C</artifactId> <version>2.0</version> </dependency> </dependencies> </dependencyManagement> ``` ## Debugging ```bash mvn dependency:tree -Dverbose # shows '(omitted for conflict with X)' lines ```

  • If a deep transitive dependency needs version 2.0 but a nearer path forces 1.0, and the code calls a 2.0-only method, what happens?
    1.0 is on the classpath, so you get a NoSuchMethodError/NoClassDefFoundError at runtime. Fix by pinning 2.0 directly or in dependencyManagement.
  • Does Maven ever pick the highest version automatically?
    No. Maven mediation is nearest-wins, not highest-wins. (Gradle, by contrast, defaults to highest-wins.)
  • How do you make Maven fail the build when versions diverge?
    Use the maven-enforcer-plugin with the dependencyConvergence rule, which fails if the same artifact resolves to conflicting versions across the graph.

Like office gossip: the version of a story you hear from the person sitting nearest your desk is the one you believe, even if someone farther away has a newer version.

saying these in an interview costs you the question

  • Saying Maven picks the highest/newest version — that's Gradle's default, not Maven's.
  • Claiming a conflict always fails the build — by default Maven silently mediates without error.
  • Forgetting the first-declared tie-breaker when depths are equal.

context