Maven resolves conflicts with a rule called nearest definition: the version closest to your project in the dependency tree wins, not the highest. So a patched library can sit in your tree while an older, vulnerable copy is the one that actually loads. You find it with dependency:tree and fix it with dependencyManagement.
How nearest wins works
The Maven dependency mechanism guide explains dependency mediation. When multiple versions of an artifact appear, Maven picks the nearest definition. Its example: if A depends on B, which depends on C, which depends on D 2.0, and A also depends on E, which depends on D 1.0, then D 1.0 is used, because the path through E is shorter. If two versions are at the same depth, the first declaration wins.
The guide adds that you can always guarantee a version by declaring it explicitly in your own POM.
Why this is a security issue
Imagine D 1.0 has a known flaw fixed in 2.0. You upgrade B to pull in the patched D, and assume the finding is gone because the POM looks right. But the build still resolves D 1.0, because E is nearer. The fix looks applied and is not.
The same thing happens in reverse after a patch release of a library you use directly: a nearer transitive path can hold an older copy.
Step by step: see it and fix it
- Print the tree and look for the artifact in question.
mvn dependency:tree -Dincludes=groupId:artifactId
The dependency plugin's tree goal shows the resolved version and where it came from.
- Decide the version you want, normally the fixed one.
- Pin it in dependencyManagement in your POM or parent POM, which sets one version regardless of what transitive paths ask for.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>library-d</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
</dependencyManagement>
- Rebuild and run the tree again to confirm the new version.
- Run your tests, since you may have overridden a version a dependency was not built for.
Prevent a repeat
The Enforcer plugin's requireUpperBoundDeps rule fails the build when the resolved version is lower than the highest version required anywhere in the tree. dependencyConvergence goes further and fails whenever different versions of one artifact appear. See our guide to the Enforcer rules.
| Approach | Effect | Cost |
|---|---|---|
| dependencyManagement pin | Forces one version everywhere | You own the choice |
| Exclusion on one path | Removes a specific route | Easy to miss other paths |
| requireUpperBoundDeps | Fails when old wins | May need several fixes up front |
Try it yourself
Click through it at your own pace, or open it full screen. Look at how a component can appear under more than one parent, which is the situation nearest wins quietly resolves.
Safeguard scans the dependencies your build resolves, and reachability analysis helps you prioritize what it finds. See also fixing vulnerabilities in Maven projects. Free scan: gold.safeguard.sh/scan.
Quick check
If I declare the patched version in my POM, will Maven use it?
Yes. A version declared explicitly in your project POM is the nearest definition, so it wins over transitive paths.
Is this specific to Maven?
Resolution rules differ by build tool. Gradle, for example, uses its own conflict resolution and lets you add constraints for transitive versions.
Sources
- Introduction to the Dependency Mechanism, Apache Maven
- Require Upper Bound Dependencies, Apache Maven Enforcer
- Dependency Convergence, Apache Maven Enforcer
- dependency:tree, Apache Maven Dependency Plugin
Last reviewed: 19 October 2026.