Maven Dependency Mechanisms

Whenever we use Maven as our build tool in our project, have we ever thought how Maven resolves dependency conflicts? Probably not, since Maven just automatically resolves them most of the time. In this article, we will learn the behind the scenes on how Maven resolves them.

Maven Dependency Mechanisms

1. Introduction

So let’s start off with a question - what is Maven’s dependency mechanism?

Apache states that:

Dependency management is a core feature of Maven. Managing dependencies for a single project is easy. Maven helps a great deal in defining, creating, and maintaining reproducible builds with well-defined classpaths and library versions.

In other words, dependency management simply maintains reproducible builds and that includes resolving version conflicts which we are about to explore in this article.

2. Dealing with Multiple Versions from the Dependencies

Majority of our projects do have this kind of complicated relationship between our dependencies and in order for Maven to maintain the correct dependency versions, it uses a method called Transitive Dependency wherein it applies basic transitivity (if dependency A => B, and B => C then A => C).

In the following section, we will explore Maven’s approaches and features that helps resolve those transitive dependencies.

2.1. Dependency Mediation

Maven has this approach that determines what version of an artifact will be selected when multiple versions are encountered as dependencies.
The diagram below is an example of a dependency having a conflict in version.

Since Maven transitively loads all dependencies from the Pom file, the dependency Log4j-1.2.2 will be included in the dependency tree despite Log4j-1.2.3 exists(and has a higher version!). To resolve this conflict, Maven uses strategies to mediate dependencies:

2.1.1. Strategy #1: Using Nearest Definition

Maven uses the closest distance from the root(the Pom file) to a specific dependency to resolve dependency conflict. This is Maven’s default strategy in resolving dependency conflicts when building our project.

Suppose our dependencies with conflict are located in different levels of the project.

Since the nearest definition for the dependency Log4j is version Log4j-1.2.3 in comparison with Log4j-1.2.2, therefore, Log4j-1.2.3 will be used as the dependency of this project. And if two dependency versions are located at the same depth in the dependency tree, the first declaration will be selected.

2.1.2. Strategy #2: Using Concrete Versions

Maven switches to another strategy as soon as it encounters a concrete version number(version number with square brackets[inclusive] / parenthesis[exclusive]) in one of the dependencies that are in conflict.

For instance, if the dependency Log4j under commons-logging has a concrete version, Maven will automatically use this concrete version to resolve the conflict.

But if the dependency with a concrete version is not available from the repository, it will propagate an error and fail.

Another scenario wherein Maven uses the same strategy is when the concrete version is in a range such as this: [1.2.2,1.2.5], how does Maven resolve this conflict?

Since the nearest dependency version is within the scope of the concrete version number(1.2.3 is within the range of 1.2.2 and 1.2.5) , Maven will instead select Log4j-1.2.3.

2.2. Dependency management

Maven allows the author of the project to inject the dependency versions of artifacts to be used when they are encountered in transitive dependencies. And it also automatically resolves those dependencies that have no version by looking for other versions in the dependency tree.

2.3. Dependency scope

Maven lets the developers to manually limit the transitivity of a dependency and to determine when a dependency is included in a classpath. Currently, there are 5 scopes available in Maven:

2.3.1. Compile

The default scope if none is specified. Dependencies with this scope are available in all classpath of a project.

2.3.2. Provided

Expect the hosting environment such as an application server (examples are Tomcat and Websphere Application Server) to provide the dependencies at runtime.

2.3.3. Runtime

Includes a dependency with this scope in the runtime and test classpath, but not the compile classpath.

2.3.4. Test

Indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases. This scope is not transitive.

2.3.5. Import

It indicates the dependency is to be replaced with the effective list of dependencies in the specified Pom’s <dependencyManagement> section. Since they are replaced, dependencies with a scope of import do not actually participate in limiting the transitivity of a dependency. This scope is only supported on a dependency of type Pom in the <dependencyManagement> section.

2.4. Excluded dependencies

Another feature that Maven is capable of is granting the author of the project to explicitly exclude specific dependencies using the tag <exclusion>.

With the diagram above, since commons-logging has an exclusion tag for its child dependency Log4j, Maven will not include commons-logging’s Log4j dependency during Build, and will instead use the other Log4j dependency.

2.5. Optional dependencies

Lastly, Maven can exclude the dependency by default and requires to explicitly include the dependency. To simply put, optional dependencies are “excluded by default”.

Consider the diagram below:

Project A has a dependency to Project B but it is set to optional. When Project X, that has a dependency to Project A attempts to reference Project B, it cannot get Project B’s reference because the tag optional prevents it from being transitively included in Project X. If the developer wants to reference Project B in Project X, the optional tag should be removed.

3. Summary

We now know that Maven has different approaches used to resolve dependency version conflicts. It also has features that lets developers address the transitivity of the dependencies. I hope this article helps clear things up on how Maven resolves dependency version conflict.

For other topics related to Maven, you can read their documentation in here.