In a Maven project we used a third-party artifact (specifically, spring-data-jpa 1.1.0.RELEASE) which depends on another artifact (spring-core) allowing any version in a range (to be precise: [3.0.7.RELEASE,4.0.0.RELEASE), see its pom-file). We had no direct dependency on spring-core.
So one day our build chose 3.1.2.RELEASE, but when 3.2.0.RC1 was released then our build suddenly picked up that version.
However, we would like to have repeatable builds: when we deliver a patch in a year's time, we don't want to pull in an updated version of spring-core, or any other indirect dependency, without at least knowing about it.
(I know that we can guide Maven to choose one specific version for spring-core, e.g., using <dependencyManagement>, but my point here is that there may be arbitrary choices hidden in indirect dependencies, and I'd like Maven to tell us about those, without having to manually check this regularly.)
Question: How can we make Maven warn us if it makes an arbitrary version choice for any indirect dependency?
As you have discovered, version ranges are evil.
The real issue is that version ranges are a siren that seduces people into thinking they are a good idea.
A version range should really be seen as a hint to the developer to allow the developer to choose the version they want from a set of versions.
The mistake in Maven was in allowing version ranges to be defined within the pom.xml in the first place as that allows for people to publish their artifacts with version ranges in them.
Once you have a dependency on an artifact which has transitive dependencies that use version ranges, there are really only two ways to solve the problem for your build (and one is just a more fancy version of the second)
Add your own dependency on the transitive dependency but with a pinned version in place of a range... e.g.
<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-orm</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
You don't need to list <optional>true</optional> dependencies as they are not transitive and similarly you don't need to list <scope>provided</scope> dependencies either for the same reason.
As for the above, but being safer by adding exclusions to the dependency in the first place, e.g.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.1.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.1.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
Of the two of these, I prefer the latter as it at least gives people a hint as to why those dependencies are being explicitly mentioned.
So to get back to your original question, the point is that you need to set up this dependency tree when you add or update dependencies in your pom.xml.
If spring-data-jpa:1.1.1.RELEASE had a completely different transitive dependency tree with different coordinates, it is when you are editing the pom.xml to update the version that you should also fix the transitives.
There is not, to my knowledge, currently any enforcer rules to support validating what you require.
I would recommend writing an enforcer rule which I would call something like: ensureTransitiveVersionRangesArePinned
That rule should do the following:
exclusion for that transitive dependencyI cannot recall if there is tooling to check that the <exclusions> are actually excluding any transitive dependencies, so you may need to investigate that.
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