Maven’s popularity is partly due to its dependency management feature. It’s easy to add a new JAR to your project and let Maven take care of importing any other JARs required as transitive dependencies.
But this is a double-edged sword. I’ve seen plenty of projects ending up with multiple versions of the same JARs – or multiple versions of the same classes in different JARs – in their classpath. In such cases the Java class loader simply picks classes from the first JAR it finds, with unpredictable results. Most of the time it’s harmless, but occasionally it causes e.g. a NoSuchMethodException at runtime in one environment, despite everything working fine in another environment.
JAR hell was well known long before Maven was invented, but Maven in some ways makes things worse. Precisely because it makes it so easy to add a library without worrying about its transitive dependencies, developers frequently do not realise what ends up in their classpath. Of course there are plenty of ways to check, from simply listing what JARs are packaged in WEB-INF/lib, to running mvn dependency:tree or using visual tools such as M2Eclipse, but there’s no guarantee developers will perform these manual checks.
For this reason I just started the maven-dependency-sanity-plugin. It performs automated checks on dependencies and will fail the build if any conflicts are detected.
As its first check, it simply prevents duplicate dependencies with the same artifactId. In my experience this is the most common cause of problems. Maven treats two libraries with the same artifactId but different groupId as different libraries. This may make sense in theory, but in practice I have never seen a single example where this behaviour is desirable.
What happens instead is that you end up with e.g.
which is clearly not what you want. And it happens quite frequently because initially most artifacts used the same string for both groupId and artifactId, then the current convention was proposed and many projects changed their groupId. (The example is not random: I’m actually the one to blame for renaming the freemarker groupId, blindly following the guidelines. 😉 )
If you look at other – more mature – package management systems like Linux DEBs and RPMs, each package is identified simply by its name, so I think Maven’s choice of a “composite key” groupId:artifactId was not a very good one, and it’s perfectly acceptable to enforce artifactId uniqueness. If anybody raises valid complaints, the plugin can always be modified to allow configurable exclusions from this rule.
Next checks I plan to add to the plugin are: declare explicit conflicts (again, like DEB/RPM packages) e.g. log4j-over-slf4j should not co-exist with log4j; enforce the same version for a set of related dependencies, e.g. all artifacts matching org.springframework:spring-*.
The definitive check would be to ensure no two JARs contain the same class, but I suspect this may be a bit too strict given the quality of artifacts currently available in Maven repositories. I will experiment with the idea nonetheless.