Fork me on GitHub

Checking a project against API signatures

Basic example

In order to check your project against an API signature, you must configure your pom.xml to reference the signature to check against:

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.24</version>
        ...
        <configuration>
          ...
          <signature>
            <groupId>___group id of signature___</groupId>
            <artifactId>___artifact id of signature___</artifactId>
            <version>___version of signature___</version>
          </signature>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

Depending on how your tooling is configured, you may alternatively want to control the version of the signatures from the dependencies or dependencyManagement> section of your project, e.g.

<project>
  ...
  <dependencyManagement>
    <dependencies>
      ...
      <dependency>
        <groupId>___group id of signature___</groupId>
        <artifactId>___artifact id of signature___</artifactId>
        <version>___version of signature___</version>
        <!-- the type is optional, safer to include it though -->
        <type>signature</type>
        <!-- if there are multiple matches, then a type of "signature" wins and "pom" trumps "jar" or none -->
      </dependency>
      ...
    </dependencies>
  </dependencyManagement>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.24</version>
        ...
        <configuration>
          ...
          <signature>
            <groupId>___group id of signature___</groupId>
            <artifactId>___artifact id of signature___</artifactId>
          </signature>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

Once you have configured your project with details of the signature to check, you have two ways to check your classes against this signature:

  • Invoke the animal-sniffer:check goal directly, e.g.
    mvn animal-sniffer:check
  • Make the checks part of your build process add an execution to your configuration, e.g.
    <project>
      ...
      <build>
        ...
        <plugins>
          ...
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>animal-sniffer-maven-plugin</artifactId>
            <version>1.24</version>
            <executions>
              ...
              <execution>
                <id>___id of execution___</id>
                ...
                <phase>test</phase>
                ...
                <goals>
                  <goal>check</goal>
                </goals>
                ...
              </execution>
              ...
            </executions>
          </plugin>
          ...
        </plugins>
        ...
      </build>
      ...
    </project>

In either case, if any of your classes reference a class, a method, or a field which is not in either the signature or your project's dependencies then a build error will be thrown.

Ignoring classes not in the signature

In certain situations you may want to reference classes which are missing from the signature you are checking. This is usually the case where you are compiling with a newer JDK than the JDK you are targetting and you are writing some code which safely makes use of the features in the newer JDK when running on the newer JDK. For example, if you have code like:

public final class MapFactory {

    private MapFactory() {
        new IllegalAccessError("This is a utility class");
    }

    public static Map newHashMap() {
        try {
            // we'd prefer the concurrent version
            return new ConcurrentHashMap();
        } catch (LinkageError e) {
            // oh dear, looks like we're running on something
            // earlier than JDK 1.5.  This will be slower
            // but still safe for concurrent access
            return Collections.synchronizedMap(new HashMap());
        }
    }
}

The above code will require JDK 1.5 or newer to compile, and can run on earlier JDKs (although with degraded performance, and we are assuming you have set the -source and -target options correctly for the earlier JDKs).

When you run animal-sniffer against the above class using the JDK 1.4 signatures, you will get a build failure because java.util.concurrent.ConcurrentHashMap is not in the JDK 1.4 signature. If you are sure that where you have used java.util.concurrent.ConcurrentHashMap you have correctly encapsulated it within try ... catch (LinkageError e) ... blocks or their equivalent, you can tell animal-sniffer to ignore the class, e.g.

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.24</version>
        ...
        <configuration>
          ...
          <ignores>
            ...
            <ignore>java.util.concurrent.ConcurrentHashMap</ignore>
            ...
          </ignores>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

We can specify multiple ignore classes, and we can also use wildcards to match multiple classes, for example, to ignore java.util.concurrent.ConcurrentHashMap, java.util.concurrent.ConcurrentMap and javax.servlet.* you would use a configuration like:

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.24</version>
        ...
        <configuration>
          ...
          <ignores>
            <ignore>java.util.concurrent.ConcurrentHashMap</ignore>
            <ignore>java.util.concurrent.ConcurrentMap</ignore>
            <ignore>javax.servlet.*</ignore>
          </ignores>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

When your minimum target JRE is 1.5 or newer

If you are targetting JRE 1.5 or newer (i.e. the lowest version of Java that your project will support is a version that supports annotations) it is preferable to annotate your LinkageError safe methods rather than using the ignores configuration element.

To annotate your methods, you need to either add animal-sniffer-annotations to your compile classpath or create your own annotation and configure it. Technically optional is the correct way, but to work around some incorrectly written Maven plugins, you may end up using scope provided.

<project>
  ...
  <dependencies>
    ...
    <dependency>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>animal-sniffer-annotations</artifactId>
      <version>1.24</version>
      <optional>true</optional>
      <!-- if you are using badly written Maven plugins then blame them and add
      <scope>provided</scope>
      -->
    </dependency>
    ...
  </dependencies>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        ...
        <configuration>
          ...
          <source>1.5</source>
          <target>1.5</target>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
  ...
</project>

Then whenever you safely reference a newer class, you just annotate the method with the configured annotation, for example:

public final class Someclass {

    ...

    @IgnoreJRERequirement
    public void doSomething() {
        try {
            // try it the JDK 6 way
        } catch (LinkageError e) {
            // fall back to the JDK 5 way
        }
    }

    ...
}

Note: if you have compiled with the org.jvnet:animal-sniffer-annotation:1.0 you do not have to change anything by default as animal-sniffer automatically detects this annotation as well (even although it is in a different package.

Let's assume you want to use your own annotation instead, com.foo.JavaGenerationDependent, you would use a configuration like:

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.24</version>
          ...
          <configuration>
            ...
              <annotations>
                <annotation>com.foo.JavaGenerationDependent</annotation>
              </annotations
            ...
          </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

Referencing classes from dependencies

By default, animal-sniffer will automatically ignore any classes and methods defined in your dependencies. This is usually what you want.

In certain rare situations, you may want to include your project dependencies by setting the ignoreDependencies parameter to false.

For example, you might be compiling your project against the JavaEE 5 specification but want to check compatibility against the J2EE 1.4 specification. You will have a signature corresponding to the exposed J2EE 1.4 api's. If you check against this signature, because all of the JavaEE 1.5 API dependencies are ignored your project will never fail even if it uses JavaEE 1.5 only methods. In this situation, you would want to turn off the default behaviour, e.g.

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.24</version>
        ...
        <configuration>
          ...
          <ignoreDependencies>false</ignoreDependencies>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>