skip to content

What does the dependencyConvergence rule do, and how does it differ from requireUpperBoundDeps?

level: seniorimportance: must knowfreq 45%

answer

  1. nearest-wins can downgrade
  2. convergence = one version everywhere
  3. upperBound = catch downgrades only
  4. fix via dependencyManagement pin
  5. report shows conflicting paths

basics

~20 s

dependencyConvergence fails the build if a transitive dependency is pulled in at more than one version anywhere in the tree. requireUpperBoundDeps is gentler: it fails only when Maven resolves a version lower than the highest one requested.

solid answer

~40 s

`dependencyConvergence` requires that every artifact resolves to a *single* version across the whole dependency graph; if two paths bring `guava:30` and `guava:32`, the build fails and the report shows both paths so you can add a `dependencyManagement` entry. It is strict and noisy on large trees. `requireUpperBoundDeps` is the pragmatic alternative: Maven's mediation uses *nearest-wins*, which can silently pick an *older* version than some path requires; this rule fails only when the resolved version is *below* the highest requested version — i.e. when you risk a downgrade and possible `NoSuchMethodError`. In short: convergence demands one version everywhere; upperBound only catches dangerous downgrades. Many teams enable `requireUpperBoundDeps` for safety and reserve full `dependencyConvergence` for libraries where strict hygiene matters.

code

xml · 4 lines
xml
<rules>
  <dependencyConvergence/>
  <requireUpperBoundDeps/>
</rules>

go deeper

for a junior

Knows both rules guard against version conflicts in transitive dependencies.

for a middle

Can read the convergence report and pin a version via dependencyManagement.

for a senior

Explains nearest-wins downgrades and chooses upperBound vs full convergence per project.

for a principal

Sets a graded dependency-hygiene policy across the org and weighs convergence noise against build maintainability.

## Background: how Maven picks a version A transitive dependency can be requested at several versions via different paths. Maven resolves conflicts with **nearest-wins (dependency mediation)**: the version declared *closest to your project* in the tree wins, regardless of whether it is newer or older. This can silently **downgrade** a library below what another path needs, causing runtime `NoSuchMethodError`/`ClassNotFoundException`. ## dependencyConvergence Fails the build if any artifact appears at **more than one version** anywhere in the resolved graph — it demands total convergence to a single version per artifact. - Pros: maximal hygiene; surfaces every conflict. - Cons: very noisy on big projects; forces you to pin many versions via `dependencyManagement`. - The failure report prints each conflicting version with its full dependency path, which tells you exactly where to add management. ## requireUpperBoundDeps Fails only when the **resolved** version is **lower** than the highest version requested by any path. In other words it catches *downgrades* — the dangerous case — and tolerates the harmless situation where the nearest (winning) version is already the highest. - Pros: low noise, targets real risk. - Cons: does not force a single version; multiple-but-safe versions still 'pass'. ## Side-by-side - Two paths want `lib:1.0` and `lib:2.0`, nearest is `2.0` → convergence FAILS (two versions exist), upperBound PASSES (resolved 2.0 is the highest). - Two paths want `lib:1.0` and `lib:2.0`, nearest is `1.0` → both FAIL (convergence: two versions; upperBound: resolved 1.0 < requested 2.0 = downgrade). ## Fixing failures Add an explicit, authoritative version in `<dependencyManagement>`: ```xml <dependencyManagement> <dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>33.0.0-jre</version> </dependency> </dependencies> </dependencyManagement> ``` This pins the version regardless of where guava is pulled in, satisfying both rules. ## Practical guidance Many teams start with `requireUpperBoundDeps` (catches the worst class of bug with little noise) and adopt full `dependencyConvergence` selectively for published libraries.

  • Why can requireUpperBoundDeps fail even when you never declared the lower version yourself?
    Because nearest-wins mediation can pick a transitively-declared older version that is closer in the tree than the path requesting the newer one, producing a silent downgrade.
  • How do you fix a convergence/upperBound failure?
    Add an explicit version in dependencyManagement so every path resolves to the same pinned version, or use an exclusion to drop the unwanted path.
  • Which rule is noisier and why?
    dependencyConvergence — it flags any artifact present at more than one version, which is common in large trees, whereas upperBound only flags actual downgrades.

saying these in an interview costs you the question

  • Claiming Maven always picks the newest version — it uses nearest-wins, which can pick an older one.
  • Treating dependencyConvergence and requireUpperBoundDeps as equivalent.
  • Fixing conflicts by editing transitive POMs instead of using dependencyManagement/exclusions.

context