Maven and wildcard exclusions

3 minute read

Once in a while I run into the problem where I don’t want to exclude a single dependency from a maven dependency, but all dependencies. For instance I recently needed a client library that, due to its parent pom, included all kinds of different framework components and dependencies it didn’t really need and me neither. And extra problem was that the transitive dependencies pointed to corrupt maven artifacts, that took a lot of time to resolve and finally fail the build.

If you want to exclude these dependencies, according to the documentation, you have to add each exclusions separately. According to the maven documentation you have to do it like this:


<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>sample.ProjectA</groupId>
  <artifactId>Project-A</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  ...
  <dependencies>
    <dependency>
      <groupId>sample.ProjectB</groupId>
      <artifactId>Project-B</artifactId>
      <version>1.0-SNAPSHOT</version>
      <exclusions>
        <exclusion>
          <groupId>sample.ProjectD</groupId> <!-- Exclude Project-D from Project-B -->
          <artifactId>Project-D</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</project>

And repeat it for each dependency. But what if you don’t need all the dependencies, or some transitive dependencies are broken, you either have to add all the dependencies to the exclusion elements or put an altered version of the artifact in your local repository that doesn’t include any of the conflicting dependencies.

Wouldn’t it be nice to be able to exclude all transitive dependencies in one go? If you look at Maven’s Jira you’re not the only one to have this same idea.

http://jira.codehaus.org/browse/MNG-2315 http://jira.codehaus.org/browse/MNG-3832

If you look at these comments, the final comment of MNG-3832 from about a week ago seems to indicate that this functionality is already in Maven! I added:


       <exclusion>
          <groupId>*</groupId>
          <artifactId>*</artifactId>
        </exclusion>

And magically all my transitive dependencies were excluded and I could build my project since the corrupt external artifacts weren’t included in the dependency graph anymore. But how could this work. Issue MNG-3832 points us into the direction where Maven supposedly determines whether to include an artifact or not. The ExludesArtifactFilter class:

public class ExcludesArtifactFilter
    extends IncludesArtifactFilter
{
    public ExcludesArtifactFilter( List<String> patterns )
    {
        super( patterns );
    }

    public boolean include( Artifact artifact )
    {
        return !super.include( artifact );
    }
}

Which uses, not so surprising, the inverse of the IncudesArtificatFilter:

    public boolean include( Artifact artifact )
    {
        String id = artifact.getGroupId() + ":" + artifact.getArtifactId();

        boolean matched = false;
        for ( Iterator<String> i = patterns.iterator(); i.hasNext() & !matched; )
        {
            // TODO: what about wildcards? Just specifying groups? versions?
            if ( id.equals( i.next() ) )
            {
                matched = true;
            }
        }
        return matched;
    }

Did you see the TODO in the last listing. Someone thought the same as the rest of us, but for now this filter checks only whether the id matches. Nothing more.

I wanted to know why this was working, and whether it was an accident it worked, or it was by design. I created a test project and started a Maven build in debug mode:


MAVEN_OPTS="-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000";
export MAVEN_OPTS
mvn install

With the remote debugger attached and a lot of stepping through the code I saw that the ExludesArtifactFilter wasn’t even called. What the newer versions of maven (don’t know from which version) apparently use to filter out exclusions is the org.sonatype.aether.util.graph.selector.ExclusionDependencySelector. This class uses the following two methods to determine whether a dependency needs to be excluded or not:

    private boolean matches( Exclusion exclusion, Artifact artifact )
    {
        if ( !matches( exclusion.getArtifactId(), artifact.getArtifactId() ) )
        {
            return false;
        }
        if ( !matches( exclusion.getGroupId(), artifact.getGroupId() ) )
        {
            return false;
        }
        if ( !matches( exclusion.getExtension(), artifact.getExtension() ) )
        {
            return false;
        }
        if ( !matches( exclusion.getClassifier(), artifact.getClassifier() ) )
        {
            return false;
        }
        return true;
    }

    private boolean matches( String pattern, String value )
    {
        return "*".equals( pattern ) || pattern.equals( value );
    }

And there it is. The wildcard “*” we use and that works. You can not only use it on groupid and artifactid but also on extension and classifier.

So now you know that when you want to exclude some troublesome set of transitive dependencies you can use wildcards. Whether you should use wildcards is a whole other discussion….

Updated: