Maven - Excluding transitive dependency from DependencyManagement - java

Consider following pom.xml using maven 3.6.2
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>foo</artifactId>
<groupId>bar</groupId>
<version>1.0.0</version>
<dependencies>
<!-- #1
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-jackson2</artifactId>
<version>3.12.0</version>
</dependency>
-->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.12</artifactId>
<version>2.4.0</version>
<scope>compile</scope>
</dependency>
<!-- #2
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-jackson2</artifactId>
<version>3.12.0</version>
</dependency>
-->
</dependencies>
<!-- #3
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-bom</artifactId>
<type>pom</type>
<version>3.12.0</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
-->
<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
</project>
If you run mvn dependency:tree | grep databind you should see:
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.10.0:compile
This is the baseline and establishes kafka_2.12 uses jackson-databind:2.10.0
If you uncomment only comment #1 and re-run, then you should see:
[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.9.3:compile
This makes sense to me and tells me jdbi3-jackson2 uses jackson-databind:2.9.9.3 and this version is used because it appears before kafka_2.12
If you uncomment only comment #2 and re-run, then you should see:
[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.10.0:compile
This also makes sense to me because jdbi3-jackson2 now appears after kafka_2.12
If you uncomment only comment #3 and re-run, then you should see:
[INFO] +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.9.3:compile
I expected 2.10.0 because I am not using anything from the bom (specifically jdbi3-jackson2)
Based on almost everything everyone says about boms, I would have thought that their version information would only be used when applying a dependency in the bom
Further, if I add the following anywhere in dependencyManagement then it goes back to 2.10.0:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
how do I detect this issue (I only found this out because kafka will fail and tell you it requires a specific jackson). My thought is to write a script/plugin that compares dependency:tree against a 'golden' copy and fail if they differ to at least make people double check their changes when modifying dependencyManagement
how do I exclude a dependency from dependencyManagement? I have done it with normal dependencies and the same exclusions tag exists for dependencyManagement, but nothing I try works. I would assume the excludes takes effect when you actually use the dependency, but I also thought the version would only take effect when you actually use the dependency as well but that assumption appears to be wrong.
because I cannot exclude it, I can force the version via the method above, but why does it work irrespective of the order when it is in dependencyManagement unlike where order matters in the normal dependencies tag?

For #1. I recommend to use maven-enforcer-plugin
For #2. You can only exclude direct transitive dependencies.
For #3. The algorithm decides based on the nearest definition which means the nearer the dependency is to your project it will be selected.

First of all, BOMs override transitive dependencies. If a dependency somewhere appear in your dependency tree and the BOM specifies a version for it, that version wins, unless:
You defined the dependency yourself with a different version.
You added a dependencyManagement entry yourself overriding the value in the BOM.
Also, of course, if BOMs overlap, only one can win.
So if you have jackson-databind as a transitive dependency and you have a BOM that includes it, you get the version from the BOM. Unless you make a dependencyManagement entry yourself that overrides the BOM.

Related

maven dependency plugin ignores dependency versions? [duplicate]

This question already has an answer here:
Maven - transitive dependencies with different versions
(1 answer)
Closed 6 years ago.
in my opinion the maven dependency plugin is misbehaving when calculating the dependency list.
Assume these 3 projects:
base1:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mygroup</groupId>
<artifactId>base1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
</project>
base2:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mygroup</groupId>
<artifactId>base2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>
combined:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mygroup</groupId>
<artifactId>combined</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>mygroup</groupId>
<artifactId>base1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mygroup</groupId>
<artifactId>base2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
Both, base1 and base2 depend on commons-lang, but each on a different version!
combined depends on both, base1 and base2.
When calling mvn dependency:list on combined, I would expect to see base1, base2 and commons-lang in versions 2.3 and 2.6, since both are used.
However the actual output is:
[INFO] The following files have been resolved:
[INFO] commons-lang:commons-lang:jar:2.3:compile
[INFO] mygroup:base1:jar:1.0-SNAPSHOT:compile
[INFO] mygroup:base2:jar:1.0-SNAPSHOT:compile
It is not even using the common-lang with the highest version number, but just the one it finds first.
How can I avoid this? I need all dependencies.
According to this official documentation (with the relevant part highlighted in bold):
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.
Therefore, Maven picks version 2.3 because it is encountered first in the dependency resolution process. Note that if you run mvn dependency:tree on the combined module, it will show which version was used and which one was omitted.
The best solution is to explicitly pick the version you want in the combined artifact, by declaring the dependency in its POM so that Maven favors it over other versions:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>mygroup</groupId>
<artifactId>combined</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>mygroup</groupId>
<artifactId>base1</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mygroup</groupId>
<artifactId>base2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency> <!-- This will override the versions in base1 and base2 -->
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
Note that Maven cannot pick two versions because in this case there would be two definitions for the same classes on your project's classpath, which can lead to unexpected issues at runtime.
Maven scans the pom from top to bottom and uses the first version it encounters.
Assuming you really need both version of commons-lang, you could put those two versions in your project and use maven to package them in your jar.
Yet, how could the compiler know if a call to StringUtils.isEmpty() calls the version 2.3 or 2.6 ?
Same discussion here.
Maven always resolves conflicts using "nearest wins" strategy. You can run the following command to see why a particular version is used:
mvn dependency:tree -Dverbose -Dincludes=commons-lang
See following for more info:
https://maven.apache.org/plugins/maven-dependency-plugin/examples/resolving-conflicts-using-the-dependency-tree.html

maven dependency without version

Recently I've been working on some improvements in a project developed some time ago, and here's what I found. A lot of dependencies in the pom files go without versions specified, and yet they are resolved. The project consists of 1 root module and 2 submodules. The Aggregator pattern is used, meaning there's no dependencyManagement section at all. The upper-project simply aggregates 2 modules and that's all it does. Subprojects don't refer to it as to a parent. They have a different parent. What I can't grasp is that neither subprojects themselves nor their parent(as a matter of fact, it doesn't have dependencyManagement either) specify versions for some of the dependencies. For instance:
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>imap</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
Can someone help me figure this out? Is maven handling versioning with some default strategy? What is that default strategy?
Ok, I think I'm gonna answer it myself. Of course I took a look at dependency:tree, but all the dependencies that I mentioned were first-level members of the tree. What I failed to notice right away, is that dependencyManagement is not present in the parent, but it is however present in the submodules, and what is more interesting it contains:
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.0.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
I've never used Spring IO Platform before, so this is a totally new concept for me. As it turns out the platform includes quite a few preconfigured dependencies:
http://docs.spring.io/platform/docs/current/reference/htmlsingle/#appendix-dependency-versions
It is impossible for maven to work without defining versions of the artifacts. They should be defined somewhere in dependencyManagement tag either in the submodule or parent. Please check your pom hierarchy. Use mvn help:effective-pom in the submodule directory of the project. Also you can use mvn dependency:tree in order to find out which artifacts - along with full artifact information including version numbers - are resolved in the result of dependency management.
Use
mvn -P<my_profile_of_interest> help:effective-pom -Dverbose
Verbose mode (Since: 3.2.0) adds XML comments containing precise reference to a place where dependency declaration is coming from.
Each maven dependency defined in the pom must have a version either directly or indirectly for example, through dependencyManagement or parent. That being said, if the version is not given, then the version provided in the dependencyManagement or the parent pom will be used.
For example: in the pom (only important sections are mentioned) given below, no version is provided for the artifact jstl. However, in the "mvn dependency:tree", it shows that jstl version 1.2 is included. And looking at the spring-boot-starter-parent, for the version 2.3.3.RELEASE pom, it includes jstl version 1.2.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
<dependencies>
....
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
....
</dependencies>
In my case if i was using Spring boot starter parent to manage all dependency and lombok version is managed by Spring boot , This problem was coming due to higher java version JAVA 11 . I exported JAVA 8 in to my compile time environment and after using JAVA 8 this problem was gone.

Why version number in maven dependency is skipped at times?

I am fairly new to maven's capabilities..
I have seen that in pom.xml where dependencies are put, at times, only groupID and artifact id are mentioned and version is skipped. why is this?
For example the below dependency is from springsource website http://spring.io/guides/gs/authenticating-ldap/
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-jndi</artifactId>
<version>1.5.5</version>
</dependency>
</dependencies>
But elsewhere in stackoverflow it was also mentioned that version is not optional. I would be glad if someone could explain this.
Yes, version is not optional.
Consider a multimodule application which has 10 modules, say module1, module2.. module10.Assume that all of these 10 projects use the spring-boot-starter-web. In case these 10 modules are interdependent, you might want to use the same version of the spring-boot-starter-web in each of these 10.
Now just imagine how complicated it would be if you were to maintain the same version number in all of these 10 pom files and then update all of them when you want to use a newer version of spring-boot-starter-web. Wouldn't it be better if this information can be managed centrally?
Maven has got something known a <dependencyManagement/> tag to get around this and centralize dependency information.
For the example which you have provided, below set of links will help you understand how the version number is resolved even though it's not present in the pom which you are looking at.
Look at the parent tag of the pom you are looking at (https://github.com/spring-guides/gs-authenticating-ldap/blob/master/complete/pom.xml)
Now lets go to that parent and see if the versions are specified in the dependencyManagement section of that pom(https://github.com/spring-projects/spring-boot/blob/master/spring-boot-starters/spring-boot-starter-parent/pom.xml). No it's not defined there as well. Now lets look at the parent of parent. https://github.com/spring-projects/spring-boot/blob/master/spring-boot-dependencies/pom.xml. Oh yea, we have got the version numbers there.
Similar to dependencyManagement, plugins can be managed in the pluginManagement section of the pom.
Hope that explains it.
Refer : dependencyManagement, pluginManagement
A few additions to the excellent answer by coderplus:
In a multi-module project, it is considered to be a good practice to configure the artifacts used by the project in the dependencyManagement of the root pom.xml so that you don't have to write versions in child module pom.xmls (like in some dependencies in your example).
It is also considered to be a good practice to declare versions of the external libraries that you use as properties and then use these properties in dependencyManagement/dependencies/dependency/version. This is more or less done here:
<properties>
<logback.version>1.1.2</logback.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
In a multi-module project, you should also declare your own artifacts in dependencyManagement.
But please do not write the version explicitly (like Spring people do here), use ${project.version} instead.
So it would have been better to write:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>${project.version}</version>
</dependency>
instead of
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>1.2.0.BUILD-SNAPSHOT</version>
</dependency>
here.
The whole purpose is DRY, don't repeat yourself. The more redundant declarations you have in your POMs, the harder they can hit. Hunting for the obsolete dependencies is so much fun.
If the <version> isn't specified then the <version> of <parent> is used. If there is no <version> in the <parent> then the <version> in the <parent> of the <parent> is used. etc.

maven: how to disable certain dependencies?

Well, I'm not talking about the well-known commons-logging problem, I know I can disable it by setting the 99.0-does-not-exist version.
My problems is, some packages are contained in different dependencies, say, aspectjlib is contained both in org.aspectj:aspectjlib and aspectj:aspectjlib. In some cases, transitive dependencies may introduce the two jars at the same time, while of different versions, e.g., org.aspectj:aspectjlib:1.7.3, aspectj:aspectjlib:1.6.1. And mis-loading aspectj:aspectjlib:1.6.1 accidentally is not my intention. So is there a way like commons-logging that I can disable aspectj:aspectjlib completely?
I tried the same trick using 99.0-does-not-exist, only to find an error from maven:
[ERROR] Failed to execute goal on project XXX: Could not resolve
dependencies for project XXX:jar:1.0.0-SNAPSHOT: The following
artifacts could not be resolved:
aspectj:aspectjlib:jar:99.0-does-not-exist,
aspectj:aspectjrt:jar:99.0-does-not-exist,
aspectj:aspectjweaver:jar:99.0-does-not-exist: Could not find artifact
aspectj:aspectjlib:jar:99.0-does-not-exist in tbmirror
(http://mvnrepo.taobao.ali.com/mvn/repository) -> [Help 1]
Well, although some repositories do provide 99.0-does-not-exist for logging system dependencies like log4j, slf4j-log4j, commons-logging, etc., this is not a universal solution.
I find a solution to do this: use 'provided' scope.
To clarify, in my example above, I have two conflicting dependencies: org.aspectj:aspectjlib:1.7.3, aspectj:aspectjlib:1.6.1, I want to disable aspectj:aspectjlib:1.6.1, I only need to put this in top-level pom:
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjlib</artifactId>
<version>1.6.1</version>
<scope>provided</scope>
</dependency>
in this way, aspectj:aspectjlib:1.6.1 will never appear in the final built lib.
You can use Maven's dependency exclusions to eliminate the version you don't want. Using your example:
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>includes-new-aspectj</groupId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>includes-old-aspectj</groupId>
<exclusions>
<exclusion>
<groupId>org.aspectj<groupId>
<artifactId>aspectjlib</artifactId>
<exclusion>
</exclusions>
</dependency>
</dependencies>
Alternatively, you can simply pin the version you desire using dependency management:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjlib</artifactId>
<version>1.7.3</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>includes-new-aspectj</groupId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>includes-old-aspectj</groupId>
</dependency>
</dependencies>
If you are not sure which dependencies include which versions, you can use this to discover that info:
mvn dependency:tree -Dincludes='org.aspectj:aspectjlib'
There is no 99.0 version for aspectj:aspectjlib, your project is configured to use wrong version, check for 99.0 in your pom.xml

Maven transitive dependecy breaks build

I have a following problem - I am trying to use apache commons-lang version 2.6 in my project (which is defined in the pom.xml) but due to transitive dependency maven always add version 3.2.1 which breaks my build.
Here is relevant part of pom.xml
<properties>
<commons-lang.version>2.6</commons-lang.version>
</properties>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.version}</version>
</dependency>
and when I try to run mvn dependency:tree I can see the reason is transitive dependency of
+- org.seleniumhq.selenium:selenium-api:jar:2.26.0:compile (version managed from 2.26.0)
+- net.sourceforge.htmlunit:htmlunit:jar:2.10:compile
+- org.apache.commons:commons-lang3:jar:3.1:compile
So I can see the problem but I have really no idea how to fix it. Thanks for any suggestions:-)
Look into using an exclusion tag inside the dependency that's causing the problem. An exclusion tag tells maven that you don't want maven to bring in an indirect dependency and is used in just this situation.
For example, if one of my dependencies A brings in a version 1.0 of B but I want to use version 2.0 of B instead, I could do this:
<dependency>
<groupId>org.mycorp</groupId>
<artifactID>A</artifactID>
<version>4.0</version>
<exclusions>
<exclusion>
<groupId>org.mycorp</groupId>
<artifactId>B</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mycorp</groupId>
<artifactID>B</artifactID>
<version>2.0</version>
</dependency>
The exclusion tag says to not bring in the indirect dependency (B 1.0) in this case. Note that you don't have to give the version. Once you've told maven not to automatically bring in that version of B, you follow with an explicit dependency that defines which version of B (version 2.0, in this case) you do want.
This particular dependency should not break your build, because Commons Lang 3 uses different package naming than Lang 2. Your code should use the classes from Lang2, while HTMLUnit will use the classes from Lang3, and both JARs can co-exist.
Perhaps you should describe how you think this breaks your build, with relevant extracts from the build output.
My collegue forgot to declare version in dependencyManagement in parent pom so that was it. Thank you for your suggestions!

Categories

Resources