Dependency convergence and the Maven enforcer plugin

|

Another great plugin for security and application stability is the Maven Enforcer plugin. You don't want to end up in JAR hell :)

You can use the Enforcer plugin for the following tasks.

Dependency convergence

Requires that dependency version numbers converge. If a project has two dependencies, A and B, both depending on the same artifact, C, this rule will fail the build if A depends on a different version of C then the version of C depended on by B.

Read more about dependency convergence in Tim Steffen's blog post. For me, adding specific versions to the pom.xmls dependencyManagement section works best, I favor active management over exclusion.

Ban circular dependencies

Checks the dependencies and fails if the groupId:artifactId combination exists in the list of direct or transitive dependencies.

I haven't really come across any occurence of this, however it is nice to have.

Ban duplicate classes

Checks the dependencies and fails if any class is present in more than one dependency.

For example two classes could be identical after the package of one class has been renamed. You should not blindly ignore duplicate classes. You should try to

  • exclude classes by excluding dependencies
  • update a library (if possible)
  • look for alternative splitted dependencies

If you add something make sure the ignored classes are binary identical!

Enforce bytecode version

Checks the dependencies transitively and fails if any class of any dependency is having its bytecode version higher than the one specified.

Example

Here's a draft for your pom.xml. You might want to add this to a dedicated build profile so it will not slow down your regular build.

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-enforcer-plugin</artifactId>
   <configuration>
      <rules>
         <!--
            Requires that dependency version numbers converge.
            If a project has two dependencies, A and B, both depending on the same artifact, C,
            this rule will fail the build if A depends on a different version of C then the
            version of C depended on by B.
        -->
         <dependencyConvergence>
            <uniqueVersions>false</uniqueVersions>
         </dependencyConvergence>
      </rules>
   </configuration>
   <executions>
      <execution>
         <id>enforce</id>
         <goals>
            <goal>enforce</goal>
         </goals>
         <phase>validate</phase>
      </execution>
      <!--
        Checks the dependencies and fails if the groupId:artifactId combination exists in the
        list of direct or transitive dependencies.
    -->
      <execution>
         <id>enforce-ban-circular-dependencies</id>
         <goals>
            <goal>enforce</goal>
         </goals>
         <configuration>
            <rules>
               <banCircularDependencies />
            </rules>
            <fail>true</fail>
         </configuration>
      </execution>
      <!--
        Checks the dependencies and fails if any class is present in more than one
        dependency.
    -->
      <execution>
         <id>enforce-ban-duplicate-classes</id>
         <goals>
            <goal>enforce</goal>
         </goals>
         <configuration>
            <rules>
               <banDuplicateClasses>
                  <ignoreClasses>
                     <!--
                            Don't just add classes here! add them as a last resort.
                            Before doing so try to:
                                * exclude classes by excluding dependencies
                                * update a library (if possible)
                                * look for alternative splitted dependencies
                            If you add something make sure the ignored classes are binary
                            identical!
                         -->
                     <ignoreClass>org.apache.juli.*</ignoreClass>
                     <ignoreClass>org.apache.commons.*</ignoreClass>
                     <ignoreClass>org.aspectj.*</ignoreClass>
                  </ignoreClasses>
                  <findAllDuplicates>true</findAllDuplicates>
               </banDuplicateClasses>
            </rules>
            <fail>true</fail>
         </configuration>
      </execution>
      <!--
        checks the dependencies transitively and fails if any class of any dependency is having
        its bytecode version higher than the one specified.
    -->
      <execution>
         <id>enforce-bytecode-version</id>
         <goals>
            <goal>enforce</goal>
         </goals>
         <configuration>
            <rules>
               <enforceBytecodeVersion>
                  <ignoredScopes>
                     <scope>test</scope>
                  </ignoredScopes>
                  <maxJdkVersion>${java.version}</maxJdkVersion>
               </enforceBytecodeVersion>
            </rules>
            <fail>true</fail>
         </configuration>
      </execution>
   </executions>
   <dependencies>
      <dependency>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>extra-enforcer-rules</artifactId>
         <version>1.0-beta-3</version>
      </dependency>
   </dependencies>
</plugin>