On larger projects, additional dependencies often tend to creep into a POM as the number of dependencies grow. As dependencies change, you are often left with dependencies that are not being used, and just as often, you may forget to declare explicit dependencies for libraries you require. Because Maven 2.x includes transitive dependencies in the compile scope, your project may compile properly but fail to run in production. Consider a case where a project uses classes from a widely used project such as Jakarta Commons BeanUtils. Instead of declaring an explicit dependency on BeanUtils, your project simply relies on a project like Hibernate that references BeanUtils as a transitive dependency. Your project may compile successfully and run just fine, but if you upgrade to a new version of Hibernate that doesn’t depend on BeanUtils, you’ll start to get compile and runtime errors, and it won’t be immediately obvious why your project stopped compiling. Also, because you haven’t explicitly listed a dependency version, Maven cannot resolve any version conflicts that may arise.
A good rule of thumb in Maven is to always declare explicit dependencies for classes referenced in your code. If you are going to be importing Commons BeanUtils classes, you should also be declaring a direct dependency on Commons BeanUtils. Fortunately, via bytecode analysis, the Maven Dependency plugin is able to assist you in uncovering direct references to dependencies. Using the updated POMs we previously optimized, let’s look to see if any errors pop up:
$ mvn dependency:analyze [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Chapter 8 Simple Parent Project [INFO] Chapter 8 Simple Object Model [INFO] Chapter 8 Simple Weather API [INFO] Chapter 8 Simple Persistence API [INFO] Chapter 8 Simple Command Line Tool [INFO] Chapter 8 Simple Web Application [INFO] Chapter 8 Parent Project [INFO] Searching repository for plugin with prefix: 'dependency'. ... [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Object Model [INFO]task-segment: [dependency:analyze] [INFO] ------------------------------------------------------------------------ [INFO] Preparing dependency:analyze [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] Nothing to compile - all classes are up to date [INFO] [dependency:analyze] [WARNING] Used undeclared dependencies found: [WARNING]javax.persistence:persistence-api:jar:1.0:compile [WARNING] Unused declared dependencies found: [WARNING]org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile [WARNING]org.hibernate:hibernate:jar:3.2.5.ga:compile [WARNING]junit:junit:jar:3.8.1:test ... [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Web Application [INFO]task-segment: [dependency:analyze] [INFO] ------------------------------------------------------------------------ [INFO] Preparing dependency:analyze [INFO] [resources:resources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:compile] [INFO] Nothing to compile - all classes are up to date [INFO] [resources:testResources] [INFO] Using default encoding to copy filtered resources. [INFO] [compiler:testCompile] [INFO] No sources to compile [INFO] [dependency:analyze] [WARNING] Used undeclared dependencies found: [WARNING]org.sonatype.mavenbook.optimize:simple-model:jar:1.0:compile [WARNING] Unused declared dependencies found: [WARNING]org.apache.velocity:velocity:jar:1.5:compile [WARNING]javax.servlet:jstl:jar:1.1.2:compile [WARNING]taglibs:standard:jar:1.1.2:compile [WARNING]junit:junit:jar:3.8.1:test
In the truncated output just shown, you can see the output of the
dependency:+analyze+ goal. This goal analyzes the project to see
whether there are any indirect dependencies, or dependencies that are
being referenced but are not directly declared. In the simple-model
project, the Dependency plugin indicates a “used undeclared
dependency” on javax.persistence:persistence-api. To investigate
further, go to the simple-model directory and run the
dependency:tree goal, which will list all of the project’s direct
and transitive dependencies:
$ mvn dependency:tree [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'dependency'. [INFO] ------------------------------------------------------------------------ [INFO] Building Chapter 8 Simple Object Model [INFO]task-segment: [dependency:tree] [INFO] ------------------------------------------------------------------------ [INFO] [dependency:tree] [INFO] org.sonatype.mavenbook.optimize:simple-model:jar:1.0 [INFO] +- org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile [INFO] | \- javax.persistence:persistence-api:jar:1.0:compile [INFO] +- org.hibernate:hibernate:jar:3.2.5.ga:compile [INFO] | +- net.sf.ehcache:ehcache:jar:1.2.3:compile [INFO] | +- commons-logging:commons-logging:jar:1.0.4:compile [INFO] | +- asm:asm-attrs:jar:1.5.3:compile [INFO] | +- dom4j:dom4j:jar:1.6.1:compile [INFO] | +- antlr:antlr:jar:2.7.6:compile [INFO] | +- cglib:cglib:jar:2.1_3:compile [INFO] | +- asm:asm:jar:1.5.3:compile [INFO] | \- commons-collections:commons-collections:jar:2.1.1:compile [INFO] \- junit:junit:jar:3.8.1:test [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------
From this output, we can see that the persistence-api dependency is
coming from hibernate. A cursory scan of the source in this module
will reveal many javax.persistence import statements confirming that
we are, indeed, directly referencing this dependency. The simple fix
is to add a direct reference to the dependency. In this example, we
put the dependency version in simple-parent's dependencyManagement
section because the dependency is linked to Hibernate, and the
Hibernate version is declared here. Eventually you are going to want
to upgrade your project’s version of Hibernate. Listing the
persistence-api dependency version near the Hibernate dependency
version will make it more obvious later when your team modifies the
parent POM to upgrade the Hibernate version.
If you look at the dependency:analyze output from the simple-web
module, you will see that we also need to add a direct reference to
the simple-model dependency. The code in simple-webapp directly
references the model objects in simple-model, and the simple-model
is exposed to simple-webapp as a transitive dependency via
simple-persist. Since this is a sibling dependency that shares both
the version and groupId, the dependency can be defined in
simple-webapps 'pom.xml using the ${project.groupId} and
${project.version}.
How did the Maven Dependency plugin uncover these issues? How does
dependency:analyze know which classes and dependencies are directly
referenced by your project’s bytecode? The Dependency plugin uses the
ObjectWeb ASM (http://asm.objectweb.org/)
of “used, undeclared dependencies” is produced.
In contrast, the list of unused, declared dependencies is a little
trickier to validate, and less useful than the “used, undeclared
dependencies.” For one, some dependencies are used only at runtime or
for tests, and they won’t be found in the bytecode. These are pretty
obvious when you see them in the output; for example, JUnit appears in
this list, but this is expected because it is used only for unit
tests. You’ll also notice that the Velocity and Servlet API
dependencies are listed in this list for the simple-web module. This
is also expected because, although the project doesn’t have any direct
references to the classes of these artifacts, they are still essential
during runtime.
Be careful when removing any unused, declared dependencies unless you have very good test coverage, or you might introduce a runtime error. A more sinister issue pops up with bytecode optimization. For example, it is legal for a compiler to substitute the value of a constant and optimize away the reference. Removing this dependency will cause the compile to fail, yet the tool shows it as unused. Future versions of the Maven Dependency plugin will provide better techniques for detecting and/or ignoring these types of issues.
You should use the dependency:analyze tool periodically to detect
these common errors in your projects. It can be configured to fail the
build if certain conditions are found, and it is also available as a
report.