We encountered a situation where Maven chose an inconsistent version of an indirect dependency, and I'd like to understand why, and how to prevent this in the future.
Our pom.xml file had the following dependencies:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
but without a direct dependency on spring-core.
Both depend on spring-core: spring-web 3.1.2.RELEASE depends on spring-core 3.1.2.RELEASE (see its pom-file), and spring-data-jpa 1.1.0.RELEASE depends on an arbitrary 3.x version of spring-core (to be precise [3.0.7.RELEASE,4.0.0.RELEASE), see its pom-file).
Combining both, I would expect Maven to choose spring-core version 3.1.2.RELEASE. However, it does not. Instead, it chooses the highest from the range [3.0.7.RELEASE,4.0.0.RELEASE), which currently is 3.2.0.RELEASE.
(Reproduction scenario: put the above pom.xml file (gist) in its own directory, and run mvn dependency:tree -Dverbose=true
: for me the result is this tree (gist). I get the same result for both Maven 2.2.1 on Linux and Maven 3.0.4 on Windows.)
This seems wrong, because it is inconsistent: a version of spring-core which spring-web's pom-file does not allow is used.
(This occurred for us when spring-core 3.2.0.RC1 became available: on the next update it was suddenly selected, and we were lucky that we got a build error because of an incompatible change between spring-core 3.1 and 3.2. But next time we may not be so lucky, and have runtime errors which are very difficult to track down.)
Urghh: I just notice that the order of <dependency>
declarations matters: if I put spring-web first, then spring-core 3.1.2.RELEASE is selected. What gives?
Question: How can we make Maven choose consistent versions of indirect dependencies, or at least warn if it makes a choice which goes against a version specified in a pom-file?
Update: I am asking for a general solution here. For this specific case, I know that I can get the correct behavior by adding a dependency in <dependencyManagement>
, specifying that I always want spring-core 3.1.2.RELEASE. However, I would like Maven to do the Right Thing (TM) without such specific declarations.
Maven picks the "nearest definition". That is, it uses the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM.
Enforcer can help developers solve dependency conflicts in Maven by analyzing all libraries declared in the POM file. The plugin uses a lot of different rules, but we are only interested in one: dependencyConvergence – ensures all dependencies converge to the same version.
By taking advantage of Maven's nearest definition logic, developers can override the version of a dependency by declaring it on the root pom. xml file.
Multiple transitive dependencies can be excluded by using the <exclusion> tag for each of the dependency you want to exclude and placing all these exclusion tags inside the <exclusions> tag in pom. xml. You will need to mention the group id and artifact id of the dependency you wish to exclude in the exclusion tag.
What you expect seems logic but Maven has no chance to do this. It just resolves dependencies without the knowledge that spring-data-jpa
might have somethig to do with spring-core
.
The dependency resolution works as describe here and is the way you already described:
Dependency mediation - this determines what version of a dependency will be used when multiple versions of an artifact are encountered. Currently, Maven 2.0 only supports using the "nearest definition" which means that it will use the version of the closest dependency to your project in the tree of dependencies. You can always guarantee a version by declaring it explicitly in your project's POM. Note that if two dependency versions are at the same depth in the dependency tree, until Maven 2.0.8 it was not defined which one would win, but since Maven 2.0.9 it's the order in the declaration that counts: the first declaration wins.
So I think the only way to prevent this situation is to be aware of it and to explecitly set the version you need in your own pom.
By the way, why do you think "This seems wrong, because it is inconsistent: a version of spring-core is used which spring-web's pom-file does not allow."?
A version specification of <version>x.y</version>
is only a recommendation (see 'Note' here) to use this version. If you intend to force to use this version you have to set <version>[x.y]</version>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With