Fork me on GitHub

Generating signatures of the Java Runtime

Basic

To generate signatures of any API, you simple construct a project with the appropriate dependencies exposed by the API and then add an execution of the animal-sniffer:build goal to your project. In the case of the Java Runtime, this is a project with no dependencies, e.g.

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>____</groupId>
  <artifactId>____</artifactId>
  <version>____</version>
  <packaging>pom</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.23</version>
        <executions>
          <execution>
            <id>___id of execution___</id>
            <phase>package</phase>
            <goals>
              <goal>build</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Then you just build this project with the appropriate Java Runtime, and it will generate the signatures of that Java Runtime.

The observant reader may have spotted that it may not always be possible to run Maven with the Java Runtime that you want to generate the signatures of. In these cases you have a number of solutions:

  • Use Maven Toolchains support and the jdk parameter to define what toolchain you require. This will use automatic boot classpath detection, which does not work for Java 1.1 or earlier and may not work on some exotic Java Runtimes.

    An example of using the jdk parameter is:

    <project>
      ...
      <build>
        ...
        <plugins>
          ...
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>animal-sniffer-maven-plugin</artifactId>
            <version>1.23</version>
            ...
            <configuration>
              ...
              <jdk>
                <version>[1.6.0,1.6.1)</version>
                <vendor>sun</vendor>
              </jdk>
              ...
            </configuration>
            ...
          </plugin>
          ...
        </plugins>
        ...
      </build>
      ...
    </project>
  • Use the javaHome parameter to specify the Java Home that you want to generate the signatures of. This will use automatic boot classpath detection, which does not work for Java 1.1 or earlier and may not work on some exotic Java Runtimes.

    An example of using the javaHome parameter is:

    <project>
      ...
      <build>
        ...
        <plugins>
          ...
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>animal-sniffer-maven-plugin</artifactId>
            <version>1.23</version>
            ...
            <configuration>
              ...
              <javaHome>C:\Program Files\Java\jdk_1.6.0_14</javaHome>
              ...
            </configuration>
            ...
          </plugin>
          ...
        </plugins>
        ...
      </build>
      ...
    </project>
  • Use the javaHomeClassPath parameter to specify the exact classpath to generate the signatures of. This is the only way to specify the Java Runtime when automatic boot classpath detection fails.

    An example of this technique is:

    <project>
      ...
      <build>
        ...
        <plugins>
          ...
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>animal-sniffer-maven-plugin</artifactId>
            <version>1.23</version>
            ...
            <configuration>
              ...
              <javaHomeClassPath>
                <javaHomeClassPath>C:\Program Files\Java\jdk_1.6.0_14\jre\lib\rt.jar</javaHomeClassPath>
                <javaHomeClassPath>C:\Program Files\Java\jdk_1.6.0_14\jre\lib\jsse.jar</javaHomeClassPath>
              </javaHomeClassPath>
              ...
            </configuration>
            ...
          </plugin>
          ...
        </plugins>
        ...
      </build>
      ...
    </project>

Generating "pure" signatures

The above examples generate signatures of the entire classpath of the JRE. That will include all the implementation classes which are not part of the public contract of the JRE. For example sun.misc.BASE64Encoder is part of Sun's JRE runtime libraries, but is not part of the JRE specification and if you run on a JRE produced by a different vendor, it is highly likely that that class will not be available to your program.

In order to ensure that the Java signatures you generate only include those classes which are officially published by the JRE, you will need to tune your signatures.

Inclusion based tuning

One technique is to only include those classes which you know are part of the JRE public specification. For example:

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.23</version>
        ...
        <configuration>
          ...
          <jdk>
            <version>[1.4.0,1.5.0)</version>
          </jdk>
          <includeClasses>
            <includeClass>java.*</includeClass>
            <includeClass>javax.*</includeClass>
            <includeClass>org.omg.*</includeClass>
            <includeClass>org.w3c.dom.*</includeClass>
            <!-- etc -->
            ...
          </includeClasses>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

This requires that you known exactly what classes are part of the JRE specification.

Exclusion based tuning

The other technique is to specify which classes are not to be included. Note that a combination of the two can also be used. For example:

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.23</version>
        ...
        <configuration>
          ...
          <jdk>
            <version>[1.4.0,1.5.0)</version>
          </jdk>
          <excludeClasses>
            <excludeClass>com.sun.*</excludeClass>
            <excludeClass>sun.*</excludeClass>
            <!-- etc -->
            ...
          </excludeClasses>
          ...
        </configuration>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

When the version of Java you require is not available

By default, if the version of Java that you require is not available, the animal-sniffer plugin will fail the build. If you are generating multiple signatures, this may not be exactly what you want. For example you may be generating signatures for JREs on Windows, Linux and MacOS from the same project. This would require building the same project at least three times and setting the skipIfNoJavaHome option to true for example

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.23</version>
        ...
        <configuration>
          ...
          <skipIfNoJavaHome>true</skipIfNoJavaHome>
          ...
        </configuration>
        ...
        <executions>
          <execution>
            <id>windows</id>
            ...
            <configuration>
              ...
              <classifier>windows</classifier>
              <jdk>
                <version>[1.4.0,1.5.0)</version>
                <os>windows</os>
              </jdk>
              ...
            </configuration>
            ...
          </execution>
          <execution>
            <id>linux
            ...
            <configuration>
              ...
              <classifier>linux</classifier>
              <jdk>
                <version>[1.4.0,1.5.0)</version>
                <os>linux</os>
              </jdk>
              ...
            </configuration>
            ...
          </execution>
        </executions>
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>

Automatic boot classpath detection

Animal sniffer uses a helper module to try and determine the boot classpath of the Java Home that has been specified. This can be done in one of two ways:

  • Indirectly via a toolchain reference using the jdk configuration option
  • Directly using the javaHome option

The helper module should work for most common versions of Java providing they are at least Java 1.2.

If the default helper module does not work for the version of Java that you need to generate signatures of, you can replace the helper module with your own module. The replacement module must meet the following specification:

  • Must be an executable JAR file
  • If the Java boot classpath cannot be detected, it must return with exit code 1.
  • If the Java boot classpath has been detected, it must print the boot class path to stdout as a single line with each element of the boot classpath separated from the next using File.pathSeparator and return with exit code 0.

When invoking the helper module, animal-sniffer will:

  • Clear the CLASSPATH environment variable in the forked execution environment.
  • Clear the JAVA_HOME environment variable in the forked execution environment.
  • Invoke the java command in the javaHome or jdk that has been configured with the options -jar and the full path to the Java boot classpath detector module specified in the configuration of the animal-sniffer plugin. Note: this module JAR file will be located in the Maven local repository.

A replacement module must be added to the dependencies section of the Animal Sniffer Plugin and the groupId and artifactId must be configured using the jbcpdGroupId and jbcpdArtifactId configuration options. For example, if your custom boot classpath detector is com.mycompany:detect-boot-classpath:1.2.0:jar then you could configure animal-sniffer to use this with the following:

<project>
  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>animal-sniffer-maven-plugin</artifactId>
        <version>1.23</version>
        ...
        <configuration>
          ...
          <jbcpdGroupId>com.mycompany</jbcpdGroupId>
          <jbcpdArtifactId>detect-boot-classpath</jbcpdArtifactId>
          ...
        </configuration>
        ...
        <dependencies>
          ...
          <dependency>
            <groupId>com.mycompany</groupId>
            <artifactId>detect-boot-classpath</artifactId>
            <version>1.2.0</version>
          </dependency>
          ...
        </dependencies>
        ...
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>