Java Dependency Conflicts: Solutions and Tips (Part I)
Introduction
In npm's Node JS, the same dependency or library may have different versions depending on the various parts of an application.
But Java and its build tools are not designed to support using two different versions of the same library at runtime. If you want multiple versions at the same time, at runtime, then you need to do classloader tricks.
What are dependency conflicts
Also called dependency hell, these terms refer to the conflict caused by using different versions of the same library, as shown above.
<!-- Project dependencies -->
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>study-maven-2</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>study-maven-3</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<groupId>org.example</groupId>
<artifactId>study-maven-2</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
</dependencies>
<groupId>org.example</groupId>
<artifactId>study-maven-3</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
</dependencies>
In this example, only one version of Guava will be imported, but which one ?
Before answering this question, we need to explain briefly another concept.
The dependency graph
A dependency graph is a graph that represents dependencies and the relations among them.
The two most used build tools -Maven and Gradle- have a very helpful tool to present all dependencies and their versions:
mvn dependency:tree -Dverbose
gradle dependencies
Resolution strategy
The strategy used to select which library version is chosen in runtime is different in Apache Maven and in Gradle.
Apache Maven
Maven uses a nearest first strategy.
It will take the shortest path to a dependency and use that version. In case there are multiple paths of the same length, the first one wins.
The main drawback of this method is that it is ordering dependent. Keeping order in a very large graph can be a challenge. For example, what if the new version of a dependency ends up having its own dependency declarations in a different order than the previous version? This could have unwanted impact on resolved versions.
Gradle
By default, Gradle will select the highest version for a dependency when there's a conflict.
You can force a particular version to be used with a custom resolutionStrategy. More info in the Gradle docs
configurations.all {
resolutionStrategy { force 'com.google.guava:guava:15.0' }
}
This does not add a dependency on guava 15.0, but forces the use of 15.0 if there is a dependency (even transitively).
Conclusion
This was a very short article that introduced some basic concepts in Java dependency management.
We will explain some solutions in the next part of these series.
References and Further Reading
Subscribe to my newsletter
Read articles from José Ramón (JR) directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
José Ramón (JR)
José Ramón (JR)
Software Engineer for quite a few years. From C programmer to Java web programmer. Very interested in automated testing and functional programming.