Adventures in Maven Dependency Conflict Resolution
by Josh Long
So, last week I wanted to build a Spring Boot starter for the Esper complex event processing engine. I love Esper, it seems like a natural solution for a common problem. So I built (and published!) the integration (it's com.joshlong:esper-spring-boot-starter:0.0.1, by the way), but you wouldn't believe the pain I experienced getting there. So, I don't know that most people will learn much from this blog but I'm going to put it out there, anyway. Here is a retrospective of the things I learned and what I did to address the issues I encountered.
I started development of the code in a sample application that I generated from the Spring Initialzr. I wanted a sample project in which to do the refactoring required to make this work, alongside the client that would use the auto configuration. Later, I thought, I'll just extract the working autoconfiguration from the main build into a separate .jar. No big deal.
I got it working and extracted as I thought I would. Then I linked the original code to the new library. It didn't work. The code that worked just fine when they were conjoined together stopped working now. It was complaining about a method missing when some code in my library interacted with code in another library. Somehow.
It's important to keep in mind how Maven works here. If there is a conflict it honors the declaration closest to the source of the build, e.g., my application that linked to the starter. So, if my code defined a version, and a library my build brought in also defined a separate version, then the library implementation would conflict with the one in my build. You need to pin down a specific version In the app. The easiest way is to use the <dependencyManagement> section, like this:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>group</groupId>
<artifactId>artifact</artifactId>
</dependency>
</dependencies>
</dependencyManagement>
Sometimes the coordinates for a dependency change, but you'll want a new version. Easy. Just exclude the dependency from the thing that's bringing it in and then declare a dependency on the new, correct dependency.
<dependency>
<groupId>groupo</groupId>
<artifactId>artifact</artifactId>
<exclusions>
<exclusion>
<groupId>old-group</groupId>
<artifactId>old-artifact</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>new-groupo</groupId>
<artifactId>new-artifact</artifactId>
</dependency>
So, I did all these things and things were still not working!
This library where the method was missing was almost the latest-and-greatest version of the library. If the current GA version of the library was 1.2.9, then I was using 1.2.6, a minor release. Why would anything break in a minor release? It was a hair rending bug indeed.
I was about to walk away but then I remembered what I just explained to you: Maven draws from the versions defined closest to the build. My build was defining the version, and it was being ignored, but the parent of my build - Spring Boot's parent dependency - was defining a version, and that's what was being honored!
This was a perfect storm of sadness:
- why was Spring Boot managing this tiny little library that I've never seen used anywhere else
- why would this library have breaking changes in a minor release?
- why didn't my
<dependencyManagement>in mypom.xmlhave precedence?
Sigh. I don't know what the resolution here is, my friends, except check your parent build and demand that your libraries do a better job of maintaining compatibility across minor binary releases!
Comments powered by Talkyard.