Using the Maven dependency tree to identify dependency conflicts

  • Post Author:
  • Post Category:Java
As your Java projects grow in complexity, so do the number of dependencies. It might not be immediately obvious that many dependencies use *other* dependencies, and the versions of those dependencies may not always play nice together. If I’m stuck on a bug the first thing I do is examine the relationship between dependencies. Do I have two that are relying on different versions of the same library? Because Maven can only pull in one version it can be important to know exactly which one it is.

mvn dependency:tree

Luckily there’s a command that will help you figure out just how deep into dependency hell you are. Here’s what the output might look like when you run mvn dependency:tree.
mvn dependency:tree

io.technicaldifficulties.sample:my-sample-project:jar:1.0-SNAPSHOT
+- org.liquibase:liquibase-core:jar:3.6.2:compile
|  +- org.yaml:snakeyaml:jar:1.18:compile
|  +- org.slf4j:slf4j-api:jar:1.7.25:compile
|  - ch.qos.logback:logback-classic:jar:1.2.3:compile
|     - ch.qos.logback:logback-core:jar:1.2.3:compile
+- org.apache.camel:camel-core:jar:2.18.5:compile
|  +- com.sun.xml.bind:jaxb-core:jar:2.2.11:compile
|  - com.sun.xml.bind:jaxb-impl:jar:2.2.11:compile
- junit:junit:jar:3.8.1:compile
That’s pretty cool. I can see the top level dependencies are Liquibase (a database migration tool), Camel (a framework for implementing Enterprise Integration Patterns), and JUnit (a library to help with testing). These are all ones that I declared directly in the pom. While I did not explicitly include  the other libraries like logback-core and jaxb-core they are still being pulled in by one of the three dependencies that I did specify. But what if Camel isn’t working quite right? What my code is failing on an error related to slf4j (logging)? The hard way to investigate this is to go into the Camel code and check what version of slf4j it’s using. The easy way is to rerun mvn dependency:tree, but this time use the -Dverbose option. Now the output looks like this:
mvn dependency:tree -Dverbose

io.technicaldifficulties.sample:my-sample-project:jar:1.0-SNAPSHOT
+- org.liquibase:liquibase-core:jar:3.6.2:compile
|  +- org.yaml:snakeyaml:jar:1.18:compile
|  +- org.slf4j:slf4j-api:jar:1.7.25:compile
|  - ch.qos.logback:logback-classic:jar:1.2.3:compile
|     +- ch.qos.logback:logback-core:jar:1.2.3:compile
|     - (org.slf4j:slf4j-api:jar:1.7.25:compile - omitted for duplicate)
+- org.apache.camel:camel-core:jar:2.18.5:compile
|  +- (org.slf4j:slf4j-api:jar:1.7.22:compile - omitted for conflict with 1.7.25)
|  +- com.sun.xml.bind:jaxb-core:jar:2.2.11:compile
|  - com.sun.xml.bind:jaxb-impl:jar:2.2.11:compile
- junit:junit:jar:3.8.1:compile
Look what we have here! Camel tried to pull in version 1.7.22 of slf4j but Maven omitted it in favor of the version that Liquibase wanted, version 1.7.25.
(org.slf4j:slf4j-api:jar:1.7.22:compile - omitted for conflict with 1.7.25)
If version 1.7.25 removed a feature that 1.7.22 had this would certainly cause problems for Camel. When it comes to situations like this, my preference is to upgrade whenever I can. For this example I would try to upgrade Camel to a newer version that uses version 1.7.25 of slf4j.

Dependency Exclusions

However, I understand an upgrade may not always possible, especially when dealing with complex projects where you pull one thread and everything starts to unravel. Maybe you just want to force version 1.17.22. To do that, you can use the <exclusions> tag to tell Liquibase to not pull in slf4j at all.
<dependency>
  <groupId>org.liquibase</groupId>
  <artifactId>liquibase-core</artifactId>
  <version>3.6.2</version>
  <exclusions>
    <exclusion>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
    </exclusion>
  </exclusions>
</dependency>
This is what happens:
mvn dependency:tree

io.technicaldifficulties.sample:my-sample-project:jar:1.0-SNAPSHOT
+- org.liquibase:liquibase-core:jar:3.6.2:compile
|  +- org.yaml:snakeyaml:jar:1.18:compile
|  - ch.qos.logback:logback-classic:jar:1.2.3:compile
|     - ch.qos.logback:logback-core:jar:1.2.3:compile
+- org.apache.camel:camel-core:jar:2.18.5:compile
|  +- org.slf4j:slf4j-api:jar:1.7.22:compile
|  +- com.sun.xml.bind:jaxb-core:jar:2.2.11:compile
|  - com.sun.xml.bind:jaxb-impl:jar:2.2.11:compile
- junit:junit:jar:3.8.1:compile
Finally Camel is free to bring in version 1.7.22 of slf4j and the dependency conflict is resolved!