Classpath conflicts cause NoSuchMethodError at run time; diagnosing them is a core Maven skill.
When the same library is reachable through several paths, Maven's nearest-wins (dependency mediation) rule picks the version whose path from your POM is shortest; for equal depth, the first declaration wins. This can silently select an older version than another path needs, leading to NoSuchMethodError or ClassNotFoundException at run time.
Diagnosing:
mvn dependency:tree -Dverboseshows omitted versions and why (omitted for conflict with 2.18.3).mvn dependency:tree -Dincludes=com.fasterxml.jackson.corenarrows the output.mvn dependency:analyzefinds undeclared and unused dependencies.mvn dependency:list -DoutputFile=...for scripting.
Fixing:
- Manage the version in
dependencyManagement: it applies to all paths, the cleanest fix. - Exclude a transitive dependency on a specific path:
<exclusions><exclusion><groupId>..</groupId><artifactId>..</artifactId></exclusion></exclusions>, for example removingcommons-loggingin favor ofjcl-over-slf4j, or an oldlog4j. - Declare the dependency directly (depth 1) to force it nearest; prefer management over this trick.
<optional>true</optional>on your own dependency stops it from propagating to consumers.
Watch for duplicate classes from relocated artifacts (javax.* vs jakarta.*, commons-lang vs commons-lang3), which no mediation can solve; the enforcer plugin's banDuplicateClasses (from extra-enforcer-rules) and dependencyConvergence rules catch these in CI.
Example
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.3</version>
<exclusions>
<exclusion> <!-- drop a transitive on this path -->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId> <!-- bridge replacing it -->
<version>2.0.17</version>
</dependency>
</dependencies>
<!-- $ mvn dependency:tree -Dverbose -Dincludes=com.fasterxml.jackson.core
[INFO] com.acme:shop-api:jar:1.0.0-SNAPSHOT
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile
[INFO] \- com.example:legacy-lib:jar:1.0:compile
[INFO] \- (com.fasterxml.jackson.core:jackson-databind:jar:2.12.7:compile
- omitted for conflict with 2.18.3)
Fix for the other direction (an old version winning): pin it in dependencyManagement. -->Remember
- Nearest path wins, then first declaration; not the highest version
- dependency:tree -Dverbose shows omitted versions and why
- Fix with dependencyManagement first, exclusions second
- Enforcer rules catch convergence and duplicate-class problems in CI
Watch out for
- Excluding a transitive dependency that the library actually needs, moving the failure to run time.