Welcome back to the Mastering Maven series. We continue our exploration of Apache Maven's dependency resolution mechanism with dependency exclusions. Up to now we know that Apache Maven will perform what's know as dependency mediation to select versions when resolving the dependency tree. We learned that transitive dependencies are resolved based on their location in the tree, and that the <dependencyManagement> block can be used to fix versions no matter where in the tree a dependency may be located. However, what happens when a dependency is not desired, is it possible to remove it from the tree? This is what dependency exclusions are for, though they should be used with caution and sparingly.
Let's assume we have the following project structure
project1 has a direct dependency on org.apache.commons:commons-lang3:3.0 while project2 also declares a dependency on org.apache.commons:commons-lang3 but using version 3.5. Next, project3 declares project2 as dependency, project4 depends on project3, and project5 depends on project2, project4, and junit:junit:4.13. Their POM files are defined as follows:
Alright, let's consume project5 but for some reason it's deemed that project2 should not be consumed at all. Do note that project2 is found twice in the dependency tree. We can exclude this dependency with a single exclusion declaration, like so
Observe that the <exclusion> element requires both <groupId>and <artifactId>. If both of these entries match then the dependency will be excluded. If there's no match then there's no exclusion but also you get no warning, thus make sure to double-check for typos though IDEs are usually helpful and will likely give you a hint. Resolving the graph yields the following result
As we can appreciate project2 is no longer found in the dependency tree despite it being in the original tree at two distinct locations. Given that exclusions must be defined in a grouping element aptly named <exclusions> you may be wondering if more than one exclusion can be defined, well the answer is yes, they can. If you happen to need multiple exclusions it may result a bit tiresome to define every single one of them; however, it's possible to use a * wildcard to exclude many dependencies. The following consumer showcases how every dependency belonging to the com.acme group may be excluded
The * wildcard is applied to <artifactId> as we don't care about that value but we do care about the specific group. Resolving the tree once again results in the following output
If you can apply the wildcard on <artifactId>, can you also apply it to <groupId>? Yes, yes you can. Here's an example where the junit artifact is excluded regardless of its group.
The resolved dependency tree shows that the exclusion works
When is this type of exclusion necessary? It only makes sense when identical artifact ids are found in the dependency tree regardless of their owning group, a not so common scenario but it may happen. Finally, if we can apply the wildcard to both artifact and group ids then we effectively tell Apache Maven that we do not want any transitive dependencies for that particular element, as it's shown in the next consumer
Resolving the dependency tree yields this result
It's recommended to use other means to customize the dependency tree, such as the <dependencyManagement> block, explicit dependencies instead of transitive, proper use of the <scope> element (both on consumers and producers), before giving into using exclusions.
Source code available at 08-dependency-exclusions.
Photo by Pexels