What should I do about dependency conflicts when using the maven-shade-plugin? - maven

I'm using the maven-shade-plugin to create an executable jar that contains all of my project's dependencies. Sometimes, these dependencies bring in dependencies of their own that clash with the dependencies of other libraries, and the maven-shade-plugin warns me that it isn't sure which version to include in the uber jar.
[WARNING] maven-shade-plugin has detected that some .class files
[WARNING] are present in two or more JARs. When this happens, only
[WARNING] one single version of the class is copied in the uberjar.
[WARNING] Usually this is not harmful and you can skeep these
[WARNING] warnings, otherwise try to manually exclude artifacts
[WARNING] based on mvn dependency:tree -Ddetail=true and the above
[WARNING] output
In general, my response to this warning is to use the <exclusions> element of the dependency declaration in my pom file to remove the offending dependencies from my project:
<!-- Amazon ElastiCache Client -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>elasticache-java-cluster-client</artifactId>
<version>1.0.61.0</version>
<exclusions>
<!-- this junit dependency clashes with our test-scoped one and causes integration tests to fail to run -->
<exclusion>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
</exclusion>
<!-- this dependency brings in two versions of cglib that clash with one another -->
<exclusion>
<groupId>jmock</groupId>
<artifactId>jmock-cglib</artifactId>
</exclusion>
<!-- newer versions of these dependencies come with dropwizard-core -->
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
When I do this, I use mvn dependency:tree to make sure that I'm excluding the lower version of the offending dependency, in hopes that the newest version is the most mature and bug free.
Cases like the one above that end up with a lot of exclusions raise two questions about this practice:
In the example above, why do I have to manually exclude junit and jmock? Both of these dependencies are marked as <scope>test</scope> in the elasticache-java-cluster-client pom.xml, so I would expect that they wouldn't be included in the jar that I get from maven.
While my practice of always taking the newer version of a dependency seems to have worked so far, I'm afraid that one of these days I'm going to break something. Is there a better way to determine which version of an dependency to keep?

Have you tried adding the maven-enforcer-plugin with the DependencyConvergence rule? This worked well for me in combination with the shade plugin. It will tell you which artifacts are bringing in different versions of the same classes. It allowed me to find out what I have to exclude.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<DependencyConvergence/>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>

Related

Maven enforcer plugin should honor dependency exclusions

How can I ensure that in my pom certain dependecies do not occur, also transitively?
This doesn't work with the maven enforcer plugin.
I added the following new dependency
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.1.0</version>
</dependency>
After restarting the app, my logback logging no longer works. A quick search shows that the dependency mentioned above comes with log4j and this messes up my logback logging.
So I don't want the transitive log4j dependency to get into my target artifact and exclude it:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.1.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
How can I check in the future whether a new dependency may also bring a transitive dependency for log4j with it?
With the maven enforcer plugin I can check this:
<bannedDependencies>
<excludes>
<exclude>org.apache.logging.log4j</exclude>
</excludes>
<searchTransitive>true</searchTransitive>
</bannedDependencies>
But it ALWAYS raises an alarm, although I have excluded it via <exlusion> mentioned above. However, if I switch to
<searchTransitive>false</searchTransitive>
then the log4j is no longer found, since I didn't declare it as a dependency directly in my pom and the enforcer check is pointless.
Now how do I get the maven enforcer plugin to honor dependency exclusions?

How to resolve "Dependency convergence error" when using maven enforcer plugin?

I am just trying to pickup with maven-enforcer-plugin using a small pom (before I jump in to my project pom which has 100+ dependencies.)
After I have added the enforcer plugin, I am seeing Dependency convergence error.
The pom.xml file is below (sorry its not tidy).
How can i fix the errors with out disabling the enforcer plugin.
Basically I want to understand the concept behind how to use dependencyConvergence rule.
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>enforcer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<!--
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.4.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>dependency-convergence</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<dependencyConvergence/>
</rules>
</configuration>
</execution>
</executions>
<configuration>
<rules>
<dependencyConvergence />
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>
Does it mean that, I have to declare each non converging dependency in the dependencyManagement explicitly as in this version of pom.xml(added dependencies to dependencyManagement).
The problem with spring-context still exists as I have added it as direct dependency and then in the dependency management with different version.
Basically - am able to fix the error, but not able to grasp the rules crystal clear yet.
fix one - pom.xml - updated the version in dependency management to the one used explicitly. So now there is no need to give the version explicitly in dependencies. But this would require me to have access to dependencyManagment of parent pom. If my statement is right, this might not be the situation every time.
fix two pom.xml - excluded spring-context from spring-security-web and it worked. But if there are a dozen of exclusion to be done, its going to be a pain.
If this is the way to go about with the convergence rule? In an enterprise project with 100+ dependencies and 100+ of their transitive dependencies, then the Bill of Materials(BOM) is gonna be quite huge and take time to build. hhhmmm. (I agree, there is going to be more control over the versions used and using property like <xyz.version>, upgrades can be done easily).
I will very much appreciate if anyone can list down the rules involving convergence.
A dependency convergence error means that
the dependency is not in dependencyManagement
there are different versions of the dependency in the dependency tree
The typical resolution is to define an entry in dependencyManagement that resolves the issue or to import an appropriate BOM into the dependencyManagement.
This is best done in the main POM of a multi module project, but also possible in modules.
Note that it is better to leave out the <version> tag in the <dependencies> section so that dependencyManagement will be used everywhere.

Maven Enforcer plugin identifies Dependency Convergence issue within Camel-CXF

The Maven enforcer plugin is identifying a code convergence issue with a 3rd party library I'm using. How can I ignore this whilst still running the enforcer plugin on the project over the rest of the project or how else should I resolve the issue without changing the library's version?
My project consumes camel-cxf 2.13.2 which it turns out depends on two separate transitive versions of jaxb-impl; 2.1.13 and 2.2.6. The enforcer plugin identifies this and fails the build.
Here is how I'm configuring the plugin:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<configuration>
<rules>
<DependencyConvergence/>
</rules>
</configuration>
</plugin>
When I run mvn enforcer:enforce I get
Rule 0: org.apache.maven.plugins.enforcer.DependencyConvergence failed with message:
Failed while enforcing releasability the error(s) are [
Dependency convergence error for com.sun.xml.bind:jaxb-impl:2.2.6 paths to dependency are:
+-com.myModule:module:18.0.0-SNAPSHOT
+-org.apache.camel:camel-cxf:2.13.2
+-org.apache.camel:camel-core:2.13.2
+-com.sun.xml.bind:jaxb-impl:2.2.6
and
+-com.myModule:module:18.0.0-SNAPSHOT
+-org.apache.camel:camel-cxf:2.13.2
+-org.apache.cxf:cxf-rt-bindings-soap:2.7.11
+-org.apache.cxf:cxf-rt-databinding-jaxb:2.7.11
+-com.sun.xml.bind:jaxb-impl:2.1.13
and
+-com.myModule:module:18.0.0-SNAPSHOT
+-org.apache.cxf:cxf-rt-management:2.7.11
+-org.apache.cxf:cxf-rt-core:2.7.11
+-com.sun.xml.bind:jaxb-impl:2.1.13
In the end I added exclusions to the specific sub dependencies which were pulling in the older, clashing versions of jaxb-impl.
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-core</artifactId>
<version>${cxf.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</exclusion>
</exclusions>
<scope>${framework.scope}</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-databinding-jaxb</artifactId>
<version>${cxf.version}</version>
<exclusions>
<exclusion>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
This way I can still run the enforcer plugin on the rest of the project and fail builds if new convergence issues are identified.
I think you don't want maven to fail the build phase when there is convergence error.
In that case you need to set fail = false flag in the configuration so it will just logs out the convergence error and proceeds with next phase.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M1</version>
<executions>
<execution>
<id>dependency-convergence</id>
<phase>install</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<DependencyConvergence />
</rules>
<fail>false</fail>
</configuration>
</execution>
<executions>
<plugin>
Note: maven-enforcer-plugin version 1.3.1 is very old. consider upgrading it to latest 3.x.x.

maven assembly pulls wrong dependency

I'm getting an unexpected version of a dependency (1.5.8) when I use the assembly plugin, but nowhere else. In my pom I have:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.0</version>
</dependency>
When I run dependency:tree or dependency:list, I see the correct version and only the correct version. When I check in Eclipse I see only the correct version.
In my assembly.xml I have:
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
In the resulting zip, I get slf4j-log4j12-1.5.8.jar. No idea where this is coming from. Any help?
Using maven 3.0.4.
This was due to a 'bad' assembly plugin version (2.2-beta-5). My pom.xml did not specify the plugin version. When I explicitly marked it as 2.4 (or the latest version when you read this!), the plugin pulled the correct dependency.
Lesson learned - If you get the following warning in your build:
[WARNING] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-whatever-plugin is missing
It is highly recommended to fix these problems because they threaten the stability of your build.
.. fix it!
You may try to delete the bad JAR (slf4j-log4j12-1.5.8.jar) from your maven repository and add the correct one there (slf4j-log4j12-1.6.0.jar). Then run your build with the --offline switch. In the moment that maven tries to get the wrong JAR, the build will fail and maven will show you from what transitive dependency it is trying to get it. Then you exclude it from the transistive dependencies with this:
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>slf4j-log4j12</groupId>
</exclusion>
</exclusions>
Check if it the JAR that you got has the correct groupId. Some people creates duplicates of common JARs for stupid and evil special purposes that may confuse maven. In special, check if you are not getting org.jboss.resteasy:slf4j-log4j12 instead. You may ban undesired dependencies using the maven-enforcer-plugin, like this:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.0</version>
<executions>
<execution>
<id>enforce-banned-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>org.slf4j:slf4j-log4j12:1.5.8</exclude> <!-- Wrong version, dude! -->
<exclude>commons-logging:*</exclude> <!-- Worst, stupidest, lamest logging framework ever! -->
<exclude>org.jboss.resteasy:slf4j-simple</exclude> <!-- Evil JAR duplication. -->
<exclude>org.jboss.resteasy:slf4j-api</exclude> <!-- Evil JAR duplication. -->
<exclude>org.jboss.resteasy:slf4j-log4j12</exclude> <!-- Evil JAR duplication. -->
<exclude>org.jboss.resteasy:jackson-core-asl</exclude> <!-- Evil JAR duplication. -->
<exclude>org.jboss.resteasy:jackson-mapper-asl</exclude> <!-- Evil JAR duplication. -->
<exclude>org.jboss.resteasy:jackson-core-lgpl</exclude> <!-- Evil JAR duplication. -->
<exclude>org.jboss.resteasy:jackson-mapper-lgpl</exclude> <!-- Evil JAR duplication. -->
<exclude>org.codehaus.jackson:jackson-core-lgpl</exclude> <!-- Two distinct packages for the exact same thing always creates conflicts. We want the ASL one. -->
<exclude>org.codehaus.jackson:jackson-mapper-lgpl</exclude> <!-- Two distinct packages for the exact same thing always creates conflicts. We want the ASL one. -->
<exclude>velocity-tools:velocity-tools</exclude> <!-- Was renamed. -->
<exclude>velocity:velocity</exclude> <!-- Was renamed. -->
<exclude>struts:struts</exclude> <!-- Was renamed. -->
<exclude>javassist:javassist</exclude> <!-- Was renamed. -->
<exclude>axis:*</exclude> <!-- Was renamed to org.apache.axis:* and wsdl4j:wsdl4j . -->
<exclude>commons-beanutils:commons-beanutils-core</exclude> <!-- Redundant package. -->
<exclude>xpp3:xpp3_min</exclude> <!-- Redundant package. -->
<exclude>xml-apis:xml-apis:2.0.0</exclude> <!-- Bad package, for some strange reason 2.0.x is inferior to 1.4.x. -->
<exclude>xml-apis:xml-apis:2.0.2</exclude> <!-- Bad package, for some strange reason 2.0.x is inferior to 1.4.x. -->
<exclude>quartz:quartz</exclude> <!-- Was renamed. -->
</excludes>
</bannedDependencies>
</rules>
</configuration>
</execution>
</executions>
</plugin>

How to exclude a dependency for a specific scope only?

I have two dependencies in my pom called A and B. Both A and B have a transitive dependency on an artifact C (cassandra-all). A and B use difference versions of C. Dependency A is the artifact astyanax.
I want to keep the Version of C that comes with B. I accomplished by adding an exclusion in A (Astyanax) for C.
Unfortunately, I want the scope of B to be 'test'. This means that with the exclusion in A, C will not be included outside of the test scope.
How can I resolve this? Can an exclusion be for a specific scope only? Alternatively, can I specify which version to use for a transitive dependency?
Example:
Here is what my pom looks like:
Artifact A (astyanax) with exclusion of dependency on Artifact C (called cassandra-all)
<dependency>
<groupId>com.netflix.astyanax</groupId>
<artifactId>astyanax</artifactId>
<version>1.0.4</version>
<exclusions>
<exclusion>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.cassandraunit</groupId>
<artifactId>cassandra-unit</artifactId>
<version>1.1.1.1</version>
<scope>test</scope>
</dependency>
So concretely: how can I include cassandra-all when I run code outside of the test scope and still keep the scope of cassandraunit test only?
I apologize if my question wasn't as clear as it could have been. The way I resolved this wasn't hard at all:
I added a separate dependency for C in my pom
I kept the exclusion of C in A
Concretely here, I just added:
<dependency>
<groupId>org.apache.cassandra</groupId>
<artifactId>cassandra-all</artifactId>
<version>1.1.5</version>
</dependency>
and also the following dependency that was missing at runtime otherwise.
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
I am not sure I understood everything, but, in any case, you should be able to achieve this with profiles.
In your pom, create a profile A in which you add your dependency A with exclusion of B and a profile B in which you'll have a dependency with exclusion of A.
On runtime, depending on which of the profile you have selected you'll include one or the other.
HIH
So concretely: how can I include cassandra-all when I run code outside of the test scope and still keep the scope of cassandraunit test only?
Use Maven POM to configure surefire-maven-plugin and change your classpath.
If what you want is only that the cassandra-all dependency be removed from the classpath while running your tests, then the following POM snippet would make the tricky:
<build>
<!-- ... -->
<plugins>
<!-- ... -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<classpathDependencyExcludes>
<classpathDependencyExcludes>
org.apache.cassandra:cassandra-all
</classpathDependencyExcludes>
</classpathDependencyExcludes>
</configuration>
</plugin>
</plugins>
</build>

Resources