Maven made it easy to manage your dependencies. long gone are the days of /lib folders and chasing after various obscure *.jar file you never directly used. these days you just declare the set of libraries you know you need and most of the time maven handles the rest through transitive dependencies and dependency version resolution.
most of the time.
sometimes though, things can get very ugly and very hard to diagnose.
there are a few common problems that can ruin your day:
- directly using a transitive dependency: if my code depends on library A, and library A in turn depends on library B then B is on my compile and runtime paths. this means that i can directly use classes from B in my code without ever declaring a direct dependency on it. this becomes a problem when the maintainers of A later decide to drop B, at which point your code stops compiling after you upgrade A.
- dependency bloat: a lot of times during development you’ll bring in various libraries (because its so easy) only to discard them later on. multiply this by the number of modules and developers working alongside you and you end up with a lot of no-longer-used dependencies that everyone’s afraid to remove because theyre not sure who’s using them.
- version uncertainty: suppose your code depends on libraries A and B, and both of thede libraries depend on library C, but on different versions of it. it is not always clear which version of C will end up on your runtime classpath and it might depend on things like the exact version of maven used to compile the project.
- duplicate class declarations on the classpath: its possible that among the dozens of libraries your project may depend on, two will define the exact same class/file and which copy gets used is at the hands of the classloader. in javaEE environments all copies might get used. and if youre especially unlucky the copies wont even be identical code. dont believe me? have a look here.
- cross-build injection attacks. this is for the more paranoid among us, but i’ve included it here for completeness’ sake. it basically comes down to how much do you trust code brought in by maven from outside – it might be intercepted and/or replaced between builds. a solution to this problem is presented by gary rowe on his blog.
now that we’ve covered what could go wrong, lets see how we can defend against it.
1st of all, i’d like to cover an invaluable tool in tracking down these issues – mvn dependency:tree. this maven invocation prints out your entire dependency tree, everything included, and is very useful in tracking down the source of any dependency issues you may have.
here’s how i ended up protecting my build:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <version>1.3.1</version> <executions> <execution> <id>enforce dependency convergence</id> <goals> <goal>enforce</goal> </goals> <configuration> <rules> <DependencyConvergence/> </rules> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.ning.maven.plugins</groupId> <artifactId>maven-duplicate-finder-plugin</artifactId> <version>1.0.6</version> <executions> <execution> <phase>verify</phase> <goals> <goal>check</goal> </goals> <configuration> <failBuildInCaseOfConflict>true</failBuildInCaseOfConflict> <ignoredResources> <!-- you will probably need something here --> <ignoredResource>changelog.txt</ignoredResource> </ignoredResources> </configuration> </execution> </executions> </plugin> </plugins> </build> <profiles> <profile> <id>StrictDependencies</id> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> <executions> <execution> <id>check for unused/undeclared dependencies</id> <goals> <goal>analyze-only</goal> </goals> <configuration> <failOnWarning>true</failOnWarning> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles>
issues #1 and #2 are addressed by the maven-dependency-plugin invocation. the invocation itself is tucked away under a profile because there may be scenarios where it’ll give false positives – for example if youre running a build without tests (-Dmaven.test.skip=true) it’ll complain about a bunch of unused test libraries. so when you know youre doing a full build, you can invoke this check by adding -PStrictDependencies to your maven command line.
issue #3 is addressed by the maven enforcer plugin which has a dependency convergence rule.
issue #4 is addressed by the maven duplicate finder plugin. you will probably need to add a few ignored files to that invocation as a lot of libraries pack stuff like changelogs.