Maven overrides transitive dependency - java

Overall applicatiom I'm using Apache HttpComponents dependency:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
But also another library uses this artifact, but different version (4.3.2, not 4.5.2):
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
</dependency>
The problem is that API between this versions is changed and I'm getting this error:
Caused by: java.lang.ClassNotFoundException: org.apache.http.ssl.SSLContexts
How I can say to maven not to override Sendgrid's version of HttpComponents (4.3.2) with 4.5.2?
EDIT: version of httpcomponents is specified in dependencyManagement section of parent pom

Given the following parent pom.xml section:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<modules>
<module>module-a</module>
<module>module-b</module>
</modules>
Indeed in module-a the dependency tree is the following, executing:
mvn dependency:tree
We get as part of the output:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) # module-a ---
[INFO] com.sample:module-a:jar:0.0.1-SNAPSHOT
[INFO] \- com.sendgrid:sendgrid-java:jar:2.0.0:compile
[INFO] +- org.json:json:jar:20140107:compile
[INFO] +- org.apache.httpcomponents:httpcore:jar:4.3.2:compile
[INFO] +- org.apache.httpcomponents:httpclient:jar:4.5.2:compile
[INFO] | +- commons-logging:commons-logging:jar:1.2:compile
[INFO] | \- commons-codec:commons-codec:jar:1.9:compile
[INFO] +- com.sendgrid:smtpapi-java:jar:1.0.0:compile
[INFO] \- org.apache.httpcomponents:httpmime:jar:4.3.4:compile
Note:
We get org.apache.httpcomponents:httpclient:jar:4.5.2:compile
We also get org.apache.httpcomponents:httpcore:jar:4.3.2:compile
A potential versons mismatch happens here between libraries of the same family
Adding then to the module-a's pom.xml the following:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.2</version>
</dependency>
</dependencies>
</dependencyManagement>
And re-running our dependency tree execution, we get as part of the output:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) # module-a ---
[INFO] com.sample:module-a:jar:0.0.1-SNAPSHOT
[INFO] \- com.sendgrid:sendgrid-java:jar:2.0.0:compile
[INFO] +- org.json:json:jar:20140107:compile
[INFO] +- org.apache.httpcomponents:httpcore:jar:4.3.2:compile
[INFO] +- org.apache.httpcomponents:httpclient:jar:4.3.2:compile
[INFO] | +- commons-logging:commons-logging:jar:1.1.3:compile
[INFO] | \- commons-codec:commons-codec:jar:1.6:compile
[INFO] +- com.sendgrid:smtpapi-java:jar:1.0.0:compile
[INFO] \- org.apache.httpcomponents:httpmime:jar:4.3.4:compile
We now get httpcore and httpclient aligned, with the versions we wanted.
Also note the httpmime to version 4.3.4, it's a fix version change, but still a misalignment (should be harmless though).
In this case it seems you are adding governance at parent level in dependencyManagement (good approach), but then at the level of one of the modules you need to override it. That can happen, but better to properly comment it, for maintenance and for the future yourself looking at it in the future.
Also note: other modules in this project would not be affected by this change, that is, they will still get version 4.5.2. If the final result of the whole multimodule build is an ear or war file, for example, carefully check what you eventually get.

It is impossible in a simple maven project to have 2 different versions of the same artifact in the classpath. So you cannot have 4.3.2 and 4.5.2 versions in the classpath simultaneously.
However there are several options... You can either
use in your project the older version (4.3.*), compatible with sendgrid-java dependency (simplest way); or
update sendgrid-java dependency, if newer one is compatible with http components 4.5.* (preferred way); or
mark sendgrid-java as a 'provided' dependency, build a separate class loader in runtime and load it with correct dependencies versions (a bit tricky, but I saw this approach in a couple bank applications)

Related

Unable to override Embedded Tomcat version in Springboot 2.3.0

I'm using Springboot 2.3.0.RELEASE
I wanted to upgrade just the tomcat version from 9.0.35 to 9.0.37 for which I tried the below steps and nothing worked :
Tried upgrading only the spring-boot-starter-tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
But even after that change, I get to see this in my dependency tree :
+- org.springframework.boot:spring-boot-starter-tomcat:jar:2.3.3.RELEASE:compile
[INFO] | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.35:compile
[INFO] | +- org.glassfish:jakarta.el:jar:3.0.3:compile
[INFO] | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.35:compile
I tried adding this property in POM, but that too didn't work:
<tomcat.version>9.0.37</tomcat.version>

mvn dependency:build-classpath not considering includeScope parameter

mvn dependency:build-classpath -DincludeScope=test
doesn't show test jars that have been added to the pom.xml
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_${scala.binary.version}</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_${scala.binary.version}</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
However, mvn -f pom.xml dependency:tree|grep tests does show these test jars. Is this some kind of a bug in maven?
Using `mvn` from path: /usr/bin/mvn
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) # java8-tests_2.10 ---
[INFO] org.apache.spark:java8-tests_2.10:pom:1.6.0-cdh5.11.0-SNAPSHOT
[INFO] | | +- org.apache.avro:avro-ipc:jar:tests:1.7.6-cdh5.11.0-SNAPSHOT:compile
[INFO] +- org.apache.spark:spark-core_2.10:test-jar:tests:1.6.0-cdh5.11.0-SNAPSHOT:test
[INFO] +- org.apache.spark:spark-streaming_2.10:test-jar:tests:1.6.0-cdh5.11.0-SNAPSHOT:test
I am trying the above in a more complicated project mixing scala and java code. On a simpler sample project this does work.

Maven transitive dependency has scope compile while when dependency has provided scope

In my project I have openejb-core dependency with scope provided. However it has transitive dependency of slf4j and its scope is compile (see screenshot). All other transitive dependencies are provided as expected.
Question: Is it bug or am I missing something?
In a sample pom I added:
<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>4.7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Then running:
mvn dependency:tree -Dincludes=org.slf4j
The output is:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) # test-junit ---
[INFO] com.sample:test-sample:jar:0.0.1-SNAPSHOT
[INFO] \- org.apache.openejb:openejb-core:jar:4.7.0:provided
[INFO] +- org.slf4j:slf4j-jdk14:jar:1.7.7:provided
[INFO] \- org.slf4j:slf4j-api:jar:1.7.7:provided
So as you can see Maven is coherent with its official documentation. The issue from your screenshot is probably on your IDE.
The table is the key point on this topic:
From it, we can see that what is transitively in scope compile or runtime goes in scope provided, what is in scope provided or test is ignored.
However, if I change my sample pom to:
<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>4.7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
</dependencies>
And re-run the dependency tree command, the output is as following:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) # test-junit ---
[INFO] com.sample:test-sample:jar:0.0.1-SNAPSHOT
[INFO] +- org.apache.openejb:openejb-core:jar:4.7.0:provided
[INFO] | \- org.slf4j:slf4j-jdk14:jar:1.7.7:provided
[INFO] \- org.slf4j:slf4j-api:jar:1.7.7:compile
Look now: it comes not as a child of the provided dependency, but at the same level.
Let's keep on.
If on my sample pom I remove the sl4f-api dependency but I add to the pom the following:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</dependencyManagement>
And re-re-run the dependency tree command, I finally get the same as your screenshot:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) # test-junit ---
[INFO] com.sample:test-sample:jar:0.0.1-SNAPSHOT
[INFO] \- org.apache.openejb:openejb-core:jar:4.7.0:provided
[INFO] +- org.slf4j:slf4j-jdk14:jar:1.7.7:provided
[INFO] \- org.slf4j:slf4j-api:jar:1.7.7:compile
Bingo: the dependencyManagement section is overriding the scope of the transitive dependency, affecting also the mediation on provided scope. This is not a bug, it's by design as in this section you define kind of governance concerning your dependencies. The diagram in this case is also correct and not misleading, since the dependency is only introduced by openejb-core which is then affected by the dependencyManagement decision to put sl4f-api in scope compile.

Maven/Eclipse/Android upgrading API version

We have been using Android API version 17 in a project and wish to upgrade to API version 19 because the application runs on Android 4.4.
We use Maven as our build environment. I have replaced all the occurrences of SDK version 17 to 19 in our AndroidManifest.xml. But I am having problems upgrading to the 4.4 platform through Maven.
I used the Android SDK Deployer tool to push the android-4.4 package into my local Maven repository. I then replaced the reference to android-4.2 to android-4.4 in our 'parent' POM.xml:
<dependencyManagement>
<dependencies>
<dependency>
<!-- <groupId>com.google.android</groupId> OLD -->
<groupId>android</groupId>
<artifactId>android</artifactId>
<!-- <version>4.2.2_r2</version> OLD -->
<version>4.4.2_r4</version>
<scope>provided</scope>
</dependency>
This change seemed to introduce a conflict. Another dependency is bringing in version 2.1_r1 of android package which is conflicting with the new 4.4.2_r4 package:
Excerpt from 'mvn dependency:tree':
.
[INFO] +- com.github.tony19:logback-android-classic:jar:1.0.10-2:compile
[INFO] | \- com.github.tony19:apktool-lib:jar:1.4.4-3:compile
[INFO] | \- com.google.android:android:jar:2.1_r1:compile
[INFO] | +- org.khronos:opengl-api:jar:gl1.1-android-2.1_r1:compile
[INFO] | +- xerces:xmlParserAPIs:jar:2.6.2:compile
[INFO] | \- xpp3:xpp3:jar:1.1.4c:compile
I noticed that if I move the <dependency> declaration of the 4.4 package above the logback-android-classic dependency declaration in pom.xml then our module will use the 4.4 dependency. However I don't think this is a proper fix, and I am running into various issues building the project as a whole.
Can those more experienced with Maven please advise on the correct way to resolve this?
Going through each affected module and adding an exclusion to the logback-android-classic dependency seems to have resolved it:
<exclusions>
<exclusion>
<artifactId>android</artifactId>
<groupId>android</groupId>
</exclusion>
<exclusion>
<artifactId>android</artifactId>
<groupId>com.google.android</groupId>
</exclusion>
</exclusions>

Maven 2 - define dependency version from transitive dependency version

I'll explain the question with my real situation.
I use logback 1.0.1 for logging, and it includes SLF4J 1.6.4 as a dependency. I also use the SLF4J API bridges for legacy logging API's (java.util.logging, log4j and commons-logging), which are not explicit dependencies. These must also (preferrably) be version 1.6.4.
Trying to make my pom.xml as neat and error-free as possible, I'd like to enforce that these API bridges be the same version as SLF4J. The only way I know is to manually define them as dependencies in my pom.xml using version 1.6.4. If I ever update logback and the required SLF4J version is raised, I'd need to remember to change the bridge API's to the proper version.
Can I somehow hook the legacy API's version to the version of the transitive dependency SLF4J?
Current pom.xml:
<properties>
<org.slf4j.version>1.6.4</org.slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.1</version>
<!-- requires SLF4J 1.6.4 -->
</dependency>
<!-- ... -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
<!-- here, how to bind this version value to SLF4J's version? -->
<scope>runtime</scope>
</dependency>
<!-- the other two bridge API's go here -->
</dependencies>
Not in a very beautiful way :/
There is the maven enforcer plugin: http://maven.apache.org/enforcer/enforcer-rules/
so you can ban the transitive dependencies and include the version you want: http://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html
If you use a property for the good version you dont need to mess around in the enforcer plugin version.
Dependency Convergence may help you. Thank you #wemu!
I have a same? problem.
You can clone https://gist.github.com/f35db1ac6dc8b6f45393.git
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.3</version>
<scope>runtime</scope> <!-- depends on slf4j-api:1.7.7 -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${unified.slf4j-api.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${unified.slf4j-api.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<unified.slf4j-api.version>1.7.7</unified.slf4j-api.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<dependencyConvergence/>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
At this point, things just work.
$ mvn -Dverbose=true dependency:tree verify
...
[INFO] test:enforcer-dependency-convergence-test:jar:0.1-SNAPSHOT
[INFO] +- ch.qos.logback:logback-classic:jar:1.1.3:runtime
[INFO] | +- ch.qos.logback:logback-core:jar:1.1.3:runtime
[INFO] | \- (org.slf4j:slf4j-api:jar:1.7.7:runtime - omitted for duplicate)
[INFO] +- org.slf4j:log4j-over-slf4j:jar:1.7.7:runtime
[INFO] | \- (org.slf4j:slf4j-api:jar:1.7.7:runtime - omitted for duplicate)
[INFO] \- org.slf4j:slf4j-api:jar:1.7.7:compile
[INFO]
[INFO] --- maven-enforcer-plugin:1.4.1:enforce (default) # enforcer-dependency-convergence-test ---
[INFO]
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
$
Now let's change the slf4j-api's version.
$ mvn -Dunified.slf4j-api.version=1.7.13 -Dverbose=true dependency:tree verify
...
[INFO] test:enforcer-dependency-convergence-test:jar:0.1-SNAPSHOT
[INFO] test:enforcer-dependency-convergence-test:jar:0.1-SNAPSHOT
[INFO] +- ch.qos.logback:logback-classic:jar:1.1.3:runtime
[INFO] | +- ch.qos.logback:logback-core:jar:1.1.3:runtime
[INFO] | \- (org.slf4j:slf4j-api:jar:1.7.7:runtime - omitted for conflict with 1.7.13)
[INFO] +- org.slf4j:log4j-over-slf4j:jar:1.7.13:runtime
[INFO] | \- (org.slf4j:slf4j-api:jar:1.7.13:runtime - omitted for conflict with 1.7.7)
[INFO] \- org.slf4j:slf4j-api:jar:1.7.13:compile
[INFO]
[INFO] --- maven-enforcer-plugin:1.4.1:enforce (default) # enforcer-dependency-convergence-test ---
[WARNING]
Dependency convergence error for org.slf4j:slf4j-api:1.7.7 paths to dependency are:
+-test:enforcer-dependency-convergence-test:0.1-SNAPSHOT
+-ch.qos.logback:logback-classic:1.1.3
+-org.slf4j:slf4j-api:1.7.7
and
+-test:enforcer-dependency-convergence-test:0.1-SNAPSHOT
+-org.slf4j:log4j-over-slf4j:1.7.13
+-org.slf4j:slf4j-api:1.7.13
and
+-test:enforcer-dependency-convergence-test:0.1-SNAPSHOT
+-org.slf4j:slf4j-api:1.7.13
[WARNING] Rule 0: org.apache.maven.plugins.enforcer.DependencyConvergence failed with message:
Failed while enforcing releasability the error(s) are [
Dependency convergence error for org.slf4j:slf4j-api:1.7.7 paths to dependency are:
+-test:enforcer-dependency-convergence-test:0.1-SNAPSHOT
+-ch.qos.logback:logback-classic:1.1.3
+-org.slf4j:slf4j-api:1.7.7
and
+-test:enforcer-dependency-convergence-test:0.1-SNAPSHOT
+-org.slf4j:log4j-over-slf4j:1.7.13
+-org.slf4j:slf4j-api:1.7.13
and
+-test:enforcer-dependency-convergence-test:0.1-SNAPSHOT
+-org.slf4j:slf4j-api:1.7.13
]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
...
$
Just don't directly depend on slf4j in your top level pom.

Categories