Unexpected Maven version selection causing runtime linkage errors
0 reputation · 14 Nov 2023, 13:42 UTC
0 reputation · 14 Nov 2023, 13:42 UTC
In a multi‑module Maven project, a dependency is declared with version 2.5.0 in the POM (or dependencyManagement) but at runtime a ClassNotFoundException indicates that an older version 2.3.1 is being loaded.
The build shows no explicit version conflict warnings, and the effective POM still lists 2.5.0 for the artifact, yet the resolved JAR on the classpath is 2.3.1.
Given Maven’s nearest‑wins rule, the version that appears in the shortest path from the root can override a higher‑level declaration, even when dependencyManagement is used.
Determine which transitive path is responsible for selecting the older version and whether any intermediate module or plugin is introducing it.
31775 reputation · 14 Nov 2023, 23:11 UTC
The most likely cause is Maven’s nearest‑wins rule picking the version from the shortest dependency path, which can be an older transitive version introduced by a sub‑module or a plugin.
mvn dependency:tree -Dverbose lists each artifact with its version and the path that brought it in.mvn dependency:tree -Dverbose and capture the output.mvn help:effective-pom or look at the plugin’s POM.After you have identified the source, you can confirm the version that will be on the classpath without editing the POM by running:
mvn help:effective-pom | grep -A2 -B2 'artifactId'Replace artifactId with the problematic artifact’s ID; the output shows the version that Maven will use.
If you cannot tell whether the older version comes from a plugin or a module, run the tree command with an inclusion filter to see only that artifact’s paths:
mvn dependency:tree -Dverbose -Dincludes=groupId:artifactIdShare the filtered output; it will reveal the exact shortest path and let you decide whether to add a version constraint in dependencyManagement or exclude the transitive dependency.
Use comments to ask for clarification. Post a solution as an answer.
31,775 reputation · 14 Nov 2023, 17:29 UTC
Run mvn dependency:tree -Dincludes=groupId:artifactId (replace with your coordinates) to show only the conflicting artifact across all modules. Each line prefixes the path depth with arrows (+- or \-); count them to confirm which module provides the shortest route. The version printed on that line is the one Maven will package.
Scope matters: nearest‑wins is evaluated separately for compile, runtime, and test scopes. If the older JAR appears only in runtime scope, dependency:tree without a scope flag may hide it. Add -Dscope=runtime to be sure.
Finally, verify the physical JAR that ends up in the built module (e.g., target/ or the assembled uber‑jar) with jar tf your-module.jar | grep artifactId or by inspecting ~/.m2/repository/…/artifactId-2.3.1.jar. This confirms the runtime classpath matches the resolved version without any POM edits.