How to make Maven warn about an arbitrary version choice? - maven

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:
Scan the list of project dependencies
Compute the transitive dependencies provided by each project dependency
If any of those transitive dependencies are version ranges then
validate that there is an exclusion for that transitive dependency
validate that there is a pinned version of the transitive dependency as a direct project dependency (may not be a failure if there is no pinned version, as you may be adding an equivalent artifact that is at a different GAV, or you may not need the dependency)... in any case if the dependency is not added back in, most likely the unit tests should catch that by triggering a CNFE so this check is probably not strictly required, but it should perhaps print a warning.
I cannot recall if there is tooling to check that the <exclusions> are actually excluding any transitive dependencies, so you may need to investigate that.

Related

Maven how to exclude dependecy which appears multiple times

I'd like to know if there is a way to exclude sdk-s3 just one time. I want to do it because I don't use it and also maven for some reason, starts downloading all the sdk-s3 versions and takes a long time to finish.
Is there a way to exlude this dependecy globally? Thanks
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sqs</artifactId>
<version>1.11.591</version>
<exclusions>
<exclusion>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>1.11.591</version>
<exclusions>
<exclusion>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-core</artifactId>
<version>1.11.591</version>
<exclusions>
<exclusion>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</exclusion>
</exclusions>
</dependency>
There is no real way to exclude a dependency globally. You can set the scope of the dependency to provided in <dependencyManagement>. This makes sure that the dependency will not be included in the resulting war or ear. It will still be on the compile classpath, though. You could also use the scope test for that.
This scope based approach is of course not what the developers of Maven intended.
Note furthermore, that Maven downloads dependencies only once and caches them in the local repository afterwards. If you want to avoid to have multiple versions, you can fix one version in the <dependencyManagement>.
As #JF Meier said, there is no real way to exclude it from all the transitive dependencies.
If the problem is that it's causing a conflict with another version of the same dependency, you override this, but explicitly defining the dependency with the version that you're interested in. This way, as it will be higher in the hierarchy, you can override all the transitive dependencies from where it's coming.

maven pom.xml exclusion for transitive and normal dependancy

i tried adding maven dependency directly, say spring context 3.0.7.release
and tried adding spring-context 3.2.8.release(this has also spring context as transitive depedency), how to exclude spring context 3.0.7. is it to comment or to remove or can we use exclusion here?
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-version} </version>
<!-- <exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
</exclusion>
</exclusions> -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
</dependencies>
No need for an exclusion here, the version of spring-expression you have in your own POM overwrites the other version, and Maven takes care that only one version is used.

Override version of transitive dependencies in Maven

I have the following in my pom
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.0-groovy-2.4</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>org.codehaus.groovy</artifactId>
<groupId>groovy-all</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.0-groovy-2.4</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>org.codehaus.groovy</artifactId>
<groupId>groovy-all</groupId>
</exclusion>
</exclusions>
</dependency>
Basically, I want to force my second and third dependencies to use the version of groovy-all that I'm setting in my first dependency. Is there a better way to do this than setting an exclusion on each of them?
Since as a first dependency you're explicitly defining a version of the groovy-all dependency, this will override the version of this dependency for all transitive dependencies needing this exact dependency. Hence, you won't have to define explicit exclusions.
To validate this, you can run the following before and after the change:
mvn dependency:tree -Dverbose
And compare the output.
Fix is to lock down the version, either via a direct dependency, or a dependency-management section.

Including "spring-security-config" into classpath makes spring hang with NoClassDef at "Aware"

I guess such error is because there are two conflicting jars in classpath. When I put into my pom these lines:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.version}</version>
</dependency>
it fails, but when i put those
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-aop</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-core</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-beans</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-expression</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
it works. These is yet another jar where these exclusions must be applied to that is spring-security-web.
Is there any more elegant way to do that? Maybe the problem lies elsewhere and this is only a "hack" or something?
Spring security has a different version scheme with spring core (I believe historically they are maintained by different organization). I suggest you don't use generic ${spring.version} variable.
Read the documentation of what minimum spring core is required for corresponding spring security version
If you believe you've got all the versioning correct, next possible cause is your maven configuration itself. Often you did not realize you've set your settings to NOT lookup from central repository / your organization internal maven repo (nexus) has a stale index not having latest version of spring artifacts

How to exclude maven dependencies?

I have a question about exclusion of maven dependencies. Consider the following
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>${spring-security.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.1.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.1.0.RELEASE</version>
</dependency>
<dependency>
I am trying to achieve a transition from Spring 3.0.6 to 3.1.0 . Spring security 3.1.0 had a dependency on spring-security-web version 3.0.6 which in turn had a dependency on spring-web 3.0.6. I need to bring it all to 3.1.0. So I exclude spring-security-web from Spring security, have a separate dependency for spring-security-web 3.1.0 which in turn excludes the spring-web 3.0.6 version and I provide a separate spring-web 3.1.0 version. This work but I feel there would be a much easier approach. I tried putting an exclusion for spring web under Spring security but it did not work.
You can utilize the dependency management mechanism.
If you create entries in the <dependencyManagement> section of your pom for spring-security-web and spring-web with the desired 3.1.0 version set the managed version of the artifact will override those specified in the transitive dependency tree.
I'm not sure if that really saves you any code, but it is a cleaner solution IMO.
Global exclusions look like they're being worked on, but until then...
From the Sonatype maven reference (bottom of the page):
Dependency management in a top-level POM is different from just
defining a dependency on a widely shared parent POM. For starters, all
dependencies are inherited. If mysql-connector-java were listed as a
dependency of the top-level parent project, every single project in
the hierarchy would have a reference to this dependency. Instead of
adding in unnecessary dependencies, using dependencyManagement allows
you to consolidate and centralize the management of dependency
versions without adding dependencies which are inherited by all
children. In other words, the dependencyManagement element is
equivalent to an environment variable which allows you to declare a
dependency anywhere below a project without specifying a version
number.
As an example:
<dependencies>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
It doesn't make the code less verbose overall, but it does make it less verbose where it counts. If you still want it less verbose you can follow these tips also from the Sonatype reference.

Resources