I recently found myself in a situation where I needed to detect whether my maven dependencies, both direct and indirect (transitive), were resolving to inconsistent versions. For example, let's say that artifact A depends on B and C, and B also depends on C. When building A, I want to know if it is picking up two different versions of C, one directly, and one transitively through B.
As with all Maven problems, "there's a plugin for that" -- in this case, the maven-enforcer-plugin. It has a variety of interesting rules, but the one that addresses the need I was having is called "dependencyConvergence".
So, I plugged it in to a top-level parent POM, so I could use it in all my projects:
<project> ... <build> ... <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>1.1.1</version> <executions> <execution> <id>enforce</id> <configuration> <rules> <DependencyConvergence /> </rules> </configuration> <goals> <goal>enforce</goal> </goals> </execution> </executions> </plugin> </plugins> ... </build> ... </project>
I use Eclipse with m2e, and I wanted this enforcement to happen in my Eclipse builds, too, so I also added:
<project> ... <build> ... <pluginManagement> <plugins> <pluginExecution> <pluginExecutionFilter> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <versionRange>[1.0,)</versionRange> <goals> <goal>enforce</goal> </goals> </pluginExecutionFilter> <action> <execute /> </action> </pluginExecution> </plugins> </pluginManagement> ... </build> ... </project>
I ran a build, and sure enough, I did have some mismatches. This is a really handy plugin -- I have a decent number of my own artifacts involved, plus a variety of 3rd party ones that show up frequently in my projects, and with all of them getting updated pretty regularly, it's pretty tough to keep track of everything.
In one case where I had a mismatch, I decided to resolve the problem by moving the specification of C's version up to a parent POM shared by A and B. Two options occurred to me:
1) I could specify a property like
and have A's and B's POMs use that inherited property in their definitions of the C dependency. Or,
2) I could put an entry for C into the parent POM's dependencyManagement section:
<dependencyManagement> <dependencies> <dependency> <groupId>org.foo</groupId> <artifactId>c</artifactId> <version>1.2.3</version> </dependency> </dependencies> </dependencyManagement>
and omit C's version from A's and B's POM.
I tried #2 first, since it seemed a little simpler. As a first step, I added the dependencyManagement entry to the parent POM and removed the version number for C from A's POM, then ran a build. Guess what: maven-enforcer-plugin stopped complaining! But wait, I didn't change B's POM yet; I should still have a discrepancy, shouldn't I? I thought that all dependencyManagement did was specify default versions for descendant POMs that omitted the version for that dependency. I checked my Maven: The Definitive Guide book to see if there was more to it. Nope, no mention of any other purpose or side effects. I checked the Sonatype web site's description, in case it was more detailed. Nope. Hmm. Then I went to the horse's mouth, the Apache docs, which say:
Dependency management - this allows project authors to directly specify the versions of artifacts to be used when they are encountered in transitive dependencies or in dependencies where no version has been specified. In the example in the preceding section a dependency was directly added to A even though it is not directly used by A. Instead, A can include D as a dependency in its dependencyManagement section and directly control which version of D is used when, or if, it is ever referenced.
[Note: that page is worth a detailed read; it has some good examples which helped firm up my understanding.]
Interesting. So the version specified in dependencyManagement serves as a default value if none is specified in a descendant POM (but doesn't override the value in a descendant POM if one is specified). However, it does override a specified value in a transitive dependency. Because of this behavior, the version of C in B's POM was being overridden, and therefore maven-enforcer-plugin didn't detect the discrepancy.
This dependencyManagement behavior has pros and cons. On one hand, you can use it to silence the maven-enforcer-plugin in situations where you can't get all the artifacts involved to use the same version (as might be the case if there are 3rd party artifacts involved). Of course, if you do that, you're taking a risk that the things could go awry at runtime, if the version you specify in dependencyManagement is incompatible in some way with an artifact that had wanted to use a different version. But sometimes you don't have a choice, and in this situation you should most likely choose the highest of the requested versions, since it's possible that it contains bug fixes or features needed by the artifact that requested it.
The downside of using dependencyManagement willy-nilly as a DRY technique is precisely that maven-enforcer-plugin will no longer give you a heads up about those discrepancies.
So what I'm doing now is this:
- I don't put dependencies in the dependencyManagement section of my top-level POM. I want to be alerted by maven-enforcer-plugin when I've got mismatches. Instead, I use version properties, as mentioned in my approach #1 above.
- When maven-enforcer-plugin notifies me of discrepancies, I try to see if I can get the artifacts involved to use the same version of the divergent dependency. If all the dependencies involved are in my own artifacts, I try to get them aligned on the same version of the dependency. If some artifacts are mine and some are from 3rd parties, I try to align my dependences with the 3rd parties, and/or look for other versions of the 3rd party artifacts that have dependency versions that align with each other, and my code.
- If after doing the above, I still have unresolvable discrepancies, I choose what I think is the "best fit" version of the problematic artifact and specify that in the dependencyManagement section of the project POM where maven-enforcer-plugin reported the problem (not in my top-level POM). I add a comment to the dependency declaration in that POM noting the issue and the workaround, so that in the future, should I upgrade to a newer version of the dependency, I'll see the note and can revisit whether the discrepancy can possibly then be resolved.