How did I get that library?!

When you’re writing Java applications, chances are you’re using Maven for dependency management. It lets you declare the artifacts you need to build your application. Those artifacts also depend on other artifacts. This means you have transitive dependencies - dependencies you didn’t declare yourself but you need them anyway.

As you add more and more dependencies, you might all of a sudden see artifacts that surprise you. For example, you’re buidling a Spring Boot app and you notice that you have both Log4J and SLF4J. Why on earth does my application have two logging libraries?

You can ask Maven to print the whole tree of direct and transitive dependencies. The command is mvn dependency:tree, but it’s output can be large.

it.mulders.brainfuck-jvm:demo-app:jar:0.1-SNAPSHOT
+- org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.1.8.RELEASE:compile
|  +- org.thymeleaf:thymeleaf-spring5:jar:3.0.11.RELEASE:compile
|  |  +- org.thymeleaf:thymeleaf:jar:3.0.11.RELEASE:compile
|  |  |  +- org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
|  |  |  \- org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
|  |  \- org.slf4j:slf4j-api:jar:1.7.28:compile
|  \- org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile

... omitted for brevity ...

\- org.springframework.boot:spring-boot-starter-test:jar:2.1.8.RELEASE:test
   +- org.springframework:spring-test:jar:5.1.9.RELEASE:test
   \- org.xmlunit:xmlunit-core:jar:2.6.3:test

In the case of Spring (Boot), even a simple application pulls in around 70 other dependencies. How to find out which of them causes Log4J or SLF4J to be included as well?

Filtering

This tree of dependencies is generated by the Maven Dependency Plugin. Its dependency:tree goal allows us to further filter its output. There are two ways to do this.

  1. With -Dexcludes=..., which removes certain artifacts from the output.
  2. With -Dincludes=..., which removes certain artifacts from the output.

For example, if we don’t want to see ByteBuddy dependencies in the output, we use mvn dependency:tree -Dexcludes=net.bytebuddy.

Both parameters accept a comma-separated list of artifacts that must (or must not) be included in the output. This means we can also ask the plugin to answer the original question with mvn dependency:tree -Dincludes=*:slf4j-api,*:log4j-api:

it.mulders.brainfuck-jvm:demo-app:jar:0.1-SNAPSHOT
+- org.springframework.boot:spring-boot-starter-web:jar:2.1.8.RELEASE:compile
|  \- org.springframework.boot:spring-boot-starter:jar:2.1.8.RELEASE:compile
|     \- org.springframework.boot:spring-boot-starter-logging:jar:2.1.8.RELEASE:compile
|        \- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
|           \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
\- org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.1.8.RELEASE:compile
   \- org.thymeleaf:thymeleaf-spring5:jar:3.0.11.RELEASE:compile
      \- org.slf4j:slf4j-api:jar:1.7.28:compile

Excellent! The output is much shorter, and it contains only the information needed to answer the question.