The Gradle Enterprise Maven extension improves your development workflow and your productivity, when developing and maintaining Apache Maven™ builds. The extension enables build scan insights and build cache acceleration.

Getting set up with the Gradle Enterprise Maven Extension

You apply the Gradle Enterprise Maven extension to your build by adding the following configuration block to a new or existing .mvn/extensions.xml file in your Maven project. The extension will be downloaded automatically from Maven Central once you run your build.

<extensions>
  <extension>
    <groupId>com.gradle</groupId>
    <artifactId>gradle-enterprise-maven-extension</artifactId>
    <version>1.0.4</version>
  </extension>
</extensions>

You can also add the extension jar into the lib/ext folder of your Maven installation. This is useful if you are packaging a custom Maven installation for your organization and you want Gradle Enterprise to be available to all your projects out of the box.

The extension is configured through one or more gradle-enterprise.xml files and the pluginManagement sections of your pom.xml files. The configuration options will be introduced over the coming sections. For the full reference of the extension’s configuration, see the Configuration reference.

Using build scans

Build scans are a record of what happened during a build, captured and visualized by Gradle Enterprise.

Build scans are an important tool for developing and maintaining Maven builds. They provide insights into exactly what your builds are doing, helping you identify problems with the build environment, performance, and more. They can also help you understand and improve the build more generally, and make collaborating with others easier.

build scan service overview
Figure 1. build scans can be published to Gradle Enterprise or scans.gradle.com

Gradle Enterprise is a commercial product for companies that can be hosted on their own systems and ships with a build scan server and a build cache backend implementation. scans.gradle.com is a build scan server available for free, hosted by Gradle Inc.

There are two aspects to working with build scans:

  • Data collection

  • Publishing

Enabling publication of build scans

Enabling publication of build scans depends on whether you are publishing to a Gradle Enterprise instance or scans.gradle.com. In the case of Gradle Enterprise, you need to specify the server’s location. In the case of scans.gradle.com, you need to accept the terms of service.

Set the location of your Gradle Enterprise instance

When you publish build scans to a Gradle Enterprise instance, you must configure the location of the Gradle Enterprise server. Add the following configuration to the gradle-enterprise.xml file:

gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <url>https://gradle-enterprise.mycompany.com</url>
  </server>
</gradleEnterprise>

The precise URL you need depends on the hostname that your Gradle Enterprise instance has been configured with. If in doubt, be sure to ask whomever manages that instance.

You may encounter a warning about an untrusted certificate when connecting to Gradle Enterprise over HTTPS. The ideal solution is for someone to add a valid SSL certificate to the Gradle Enterprise instance, but we recognise that you may not be able to do that. In this case, set the allowUntrusted option to true:

gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <url>https://gradle-enterprise.mycompany.com</url>
    <allowUntrusted>true</allowUntrusted>
  </server>
</gradleEnterprise>

This is a convenient workaround, but you shouldn’t use it as a long-term solution.

Accept the scans.gradle.com terms of service

In order to publish to scans.gradle.com, you need to accept the terms of service, by adding the following configuration to the gradle-enterprise.xml:

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <termsOfService>
      <url>https://gradle.com/terms-of-service</url>
      <accept>true</accept>
    </termsOfService>
  </buildScan>
</gradleEnterprise>

Be sure to check the terms of service at the URL shown in the above fragment.

Once you have accepted the terms of service, you can start publishing build scans to scans.gradle.com.

If you don’t accept the terms of service, as explained above, you will be asked to agree to the terms of service on the command line, before any attempt of publishing a build scan. This can be useful if you share your gradle-enterprise.xml file with others, and you want everyone to explicitly accept those terms of service.

Controlling when build scans are published

Once you’ve gone through the initial setup of the previous section, you are ready to start publishing build scans. But when should you publish them? Every time you run a build? Only when the build fails? It’s up to you. The Gradle Enterprise Maven extension has several options that allow you to use whatever approach works best.

Publishing every build run

This is the default. There are many advantages to publishing build scans regularly, such as being able to track the behavior and performance of a build over time. It makes no sense relying on ad-hoc publishing of scans in such a situation as it’s easy to forget on the command line.

When you want to publish every build run, you can use the ALWAYS setting, which is also set by default:

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <publish>ALWAYS</publish>
  </buildScan>
</gradleEnterprise>

This approach means that you get a build scan for every successful and failed build that runs, including from your continuous integration infrastructure and your developers.

Publishing every failed build

If you only want to publish build scans when the build fails, use the following option:

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <publish>ON_FAILURE</publish>
  </buildScan>
</gradleEnterprise>

Publishing on demand

We imagine that when you first start experimenting with build scans, you won’t want to publish them all the time until you become familiar with the implications. Even then, you may have good reason not to go all-in and automate the process. That’s where one-off build scans come in.

If you only want to publish build scans when explicitly requested, use the following option:

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <publish>ON_DEMAND</publish>
  </buildScan>
</gradleEnterprise>

You can then publish a build scan by passing the -Dscan system property to Maven.

$ mvn clean verify -Dscan

Regardless of the configured publication setting, you can always publish a build scan by passing the -Dscan system property to Maven. Passing -Dscan always takes precedence over the publication setting configured in the gradle-enterprise.xml file.

Publishing only for CI builds

If you want to publish build scans only from your CI system, you have to use a specific gradle-enterprise.xml file located at <maven-home>/conf/gradle-enterprise.xml in your CI agents. Alternatively, you can define the -Dgradle.config.user system property on your CI builds to indicate the location of the gradle-enterprise.xml file that needs to be used in this case.

Extending build scans

You can easily include extra custom information in your build scans in the form of tags, links and values. This is a very powerful mechanism for capturing and sharing information that is important to your build and development process.

This information can be anything you like. You can tag all builds run by your continuous integration tool with a CI tag. You can capture the name of the environment that the build published to as a value. You can link to the source revision for the build in an online tool such as GitHub. The possibilities are endless.

You can see how the custom data appears in figure 2:

scan with custom data
Figure 2. A build scan containing the different types of custom data

Gradle Enterprise allows listing and searching across all of the build scans in the system. You can find and filter build scans by tags and custom values, in addition to project name, outcome and other properties. In figure 3, for example, we are filtering for all build scans that have the tag "local" and a git branch name of "master":

build scan filtered list
Figure 3. A filtered list of build scans in Gradle Enterprise

Adding tags

Tags are typically used to indicate the type or class of a build, or a key characteristic. They are prominent in the user interface and quickly inform a user about the nature of a build. A build can have zero or more tags.

They can be added in the gradle-enterprise.xml file and/or your project POM.

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <tags>
      <tag>my tag</tag>
    </tags>
  </buildScan>
</gradleEnterprise>
pom.xml
<project>
  ...
  <plugins>
    <plugin>
      <groupId>org.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <version>1.0.4</version>
      <configuration>
        <gradleEnterprise>
          <buildScan>
            <tags>
              <tag>my tag</tag>
            </tags>
          </buildScan>
        </gradleEnterprise>
      </configuration>
    <plugin>
  </plugins>
</project>

Note that the order in which you declare the tags doesn’t affect the build scan view. They are displayed in alphabetical order, with any all-caps labels displayed before the rest.

You can see the effect of a custom tag in figure 2.

The Gradle Enterprise Maven extension imposes limits on captured tags:

  • maximum tag count: 50

  • maximum tag length: 200 characters

Builds rarely live in isolation. Where does the project source live? Is there online documentation for the project? Where can you find the project’s issue tracker? If these exist and have a URL, you can add them to the build scan.

They can be added in the gradle-enterprise.xml file and/or your project POM.

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <links>
      <link>
        <name>my link<name>
        <url>http://my-site.com</url>
      </link>
    </links>
  </buildScan>
</gradleEnterprise>
pom.xml
<project>
  ...
  <plugins>
    <plugin>
      <groupId>org.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <version>1.0.4</version>
      <configuration>
        <gradleEnterprise>
          <buildScan>
            <links>
              <link>
                <name>my link<name>
                <url>http://my-site.com</url>
              </link>
            </links>
          </buildScan>
        </gradleEnterprise>
      </configuration>
    <plugin>
  </plugins>
</project>

The <name> is simply a string identifier that you choose and that means something to you.

You can see the effect of a custom link in figure 2, which shows how a label CVS becomes a hyperlink that anyone viewing the build scan can follow.

The Gradle Enterprise Maven extension imposes limits on captured links:

  • maximum link count: 20

  • maximum link label length: 100 characters

  • maximum link url length: 1,000 characters

Adding custom values

Some information just isn’t useful without context. What does "1G" mean? You might guess that it represents 1 gigabyte, but of what? It’s only when you attach the label "Max heap size for build" that it makes sense. The same applies to git commit IDs, for example, which could be interpreted as some other checksum without a suitable label.

Custom values are designed for these cases that require context. They’re standard key-value pairs, in which the key is a string label of your choosing and the values are also strings, often evaluated from the build environment.

They can be added in the gradle-enterprise.xml file and/or your project POM.

gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <values>
      <value>
        <name>my name<name>
        <value>my value</value>
      </value>
    </values>
  </buildScan>
</gradleEnterprise>
pom.xml
<project>
  ...
  <plugins>
    <plugin>
      <groupId>org.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <version>1.0.4</version>
      <configuration>
        <gradleEnterprise>
          <buildScan>
            <values>
              <value>
                <name>Build Number<name>
                <value>${project.buildNumber}</value>
              </value>
            </values>
          </buildScan>
        </gradleEnterprise>
      </configuration>
    <plugin>
  </plugins>
</project>

The above example demonstrates how you can read the build number from a project property—assuming you have your build set up for this—and attach it to the build scans as a custom value.

As with tags, you can filter build scans by custom values in Gradle Enterprise.

The Gradle Enterprise Maven extension imposes limits on captured custom values:

  • maximum custom value count: 1,000

  • maximum custom value key length: 1,000 characters

  • maximum custom value value length: 100,000 characters

Providing custom data via system properties

The examples up to this point have shown how to add data in your gradle-enterprise.xml file or project POM. The Gradle Enterprise Maven extension also allows you to inject any form of custom data through the use of specially named system properties. This can help you keep your build files clean of too much environment-specific information that may not be relevant to most of the users of the build.

These system properties take the following forms, depending on whether you want to inject a tag, a link or a custom value:

-Dscan.tag.<tag>
-Dscan.link.<name>=<URL>
-Dscan.value.<name>=<value>

Here are some concrete examples, which assume that the build has been configured to publish build scans automatically:

$ mvn package -Dscan.tag.CI
$ mvn package -Dscan.link.VCS=https://github.com/myorg/my-super-project/tree/my-new-feature
$ mvn package "-Dscan.value.CI Build Type=QA_Build"

This feature is particularly useful for continuous integration builds as you can typically easily configure your CI tool to specify system properties for a build. It’s even common for CI tools to be able to inject system properties into a build that are interpolated with information from the CI system, such as build number.

$ mvn package "-Dscan.value.buildNumber=$CI_BUILD_NUMBER"

You might be interested to add custom tags, links or custom values only if a certain condition is met. You can achieve this, for example, by using the Groovy Maven Plugin, and codify your logic in Groovy to conditionally add custom data through the exposed BuildScanApi.

pom.xml
<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.gmavenplus</groupId>
        <artifactId>gmavenplus-plugin</artifactId>
        <version>1.6.2</version>
        <executions>
          <execution>
            <id>git-status</id>
            <phase>validate</phase>
            <goals>
              <goal>execute</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <scripts>
            <script>file:///${project.basedir}/buildScanUserData.groovy</script>
          </scripts>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.5.3</version>
            <type>pom</type>
            <scope>runtime</scope>
          </dependency>
          <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
            <scope>runtime</scope>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>
buildScanUserData.groovy
import org.apache.commons.lang3.StringUtils

// Look up the build scan API
def buildScan = session.lookup("com.gradle.maven.extension.api.scan.BuildScanApi")

// Capture if the build is from CI as a tag
def ci = System.getenv('CI')
if (ci) {
  buildScan.tag('CI')
} else {
  buildScan.tag('Local')
}

// Capture VCS branch of the build as a link
buildScan.link('VCS', "https://github.com/myorg/sample/tree/${System.getProperty('vcs.branch')}")

// Capture Git status as custom value
def status = StringUtils.chomp('git status --porcelain'.execute().text)
if (status) {
  buildScan.value('Git status', status)
}

See API reference for the complete API documentation.

Using the build cache

The build cache speeds up your builds by reusing outputs from any previous build, on any machine that is connected to the same build cache backend. It does this by reducing the inputs of a goal execution down to a strong hash key and storing the execution’s output under that key. It supports a local cache that allows other subsequent builds on the same machine to reuse the outputs whenever they execute a goal with the same inputs. The full benefit of the build cache is realized when also using the remote backend that Gradle Enterprise provides. This remote cache allows you to share cached outputs across your whole team, including local and CI builds.

In the diagram below, you can see the flow of CI agents pushing to the remote cache, and developers pulling from the remote cache.

caching typical scenario
  1. A goal is executed on a CI server. The build is configured to push to a configured remote cache, so that outputs can be reused by other CI pipeline builds and developer builds.

  2. A developer executes the same goal with a local change to a file. The Gradle Enterprise Maven extension tries to load the output from the local cache, then the remote cache. Neither contains a matching entry due to the local change, so the goal executes. The output is stored in the configured local cache. Outputs stored in the local cache can be reused in subsequent builds on that developer’s machine.

  3. A second developer executes that goal without any local changes from the commit that CI built. This time the remote cache lookup is a hit, the cached output is downloaded and directly copied to the workspace and the local cache. The goal does not need to be executed.

The build caching functionality for Maven requires a Gradle Enterprise license. The free scans.gradle.com server does not allow using the build cache.

Configuring the build cache

In order to use build caching for Apache Maven, you need to configure the location of your Gradle Enterprise server. Add the following configuration to the gradle-enterprise.xml:

gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <url>https://gradle-enterprise.mycompany.com</url>
  </server>
</gradleEnterprise>

The precise URL you need depends on the hostname that your Gradle Enterprise instance has been configured with. If in doubt, be sure to ask whomever manages that instance.

You may encounter a warning about an untrusted certificate when connecting to Gradle Enterprise over HTTPS. The ideal solution is for someone to add a valid SSL certificate to the Gradle Enterprise instance, but we recognise that you may not be able to do that. In this case, set the allowUntrusted option to true:

gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <url>https://gradle-enterprise.mycompany.com</url>
    <allowUntrusted>true</allowUntrusted>
  </server>
</gradleEnterprise>

This is a convenient workaround, but you shouldn’t use it as a long-term solution.

Configuring the local cache

The extension uses a local build cache to store build outputs in the local filesystem. It prevents network roundtrips by storing both outputs that local builds created, as well as outputs that were downloaded from the remote build cache.

Disabling the local cache

The local build cache is enabled by default. This can be changed by setting the enabled option to false.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <local>
      <enabled>false</enabled>
    </local>
  </buildCache>
</gradleEnterprise>

You can also use the -Dgradle.cache.local.enabled=false system property to achieve the same effect.

Changing the local cache directory

The local cache is located at ${user.home}/.m2/.gradle-enterprise/build-cache by default. This can be changed by setting the directory option.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <local>
      <directory>/path/to/local/build-cache</directory>
    </local>
  </buildCache>
</gradleEnterprise>

It is a common practice in large organizations to put the user home on a network share. Since the underlying motivation of a local build cache is to prevent network roundtrips, you should explicitly configure the local cache directory to a path on the local filesystem.

Configuring local cache cleanup

To prevent the local cache from growing in size indefinitely, the local cache directory is cleaned up periodically. By default, the cleanup interval is 24 hours and the retention time is 7 days. The cleanup can be disabled by setting the enabled option to false.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <local>
      <cleanup>
        <enabled>false</enabled>
      </cleanup>
    </local>
  </buildCache>
</gradleEnterprise>

The cleanup interval and retention time are controlled by the interval and retention options. The formats accepted are based on the ISO-8601 duration format PnDTnHnMn.nS.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <local>
      <cleanup>
        <retention>P30D</retention>
        <interval>P10D</interval>
      </cleanup>
    </local>
  </buildCache>
</gradleEnterprise>
Working offline

In order to work offline, the extension needs to have run in online mode at least once in the past 24 hours to check whether the given Gradle Enterprise server allows build caching for Maven. The result of this check is stored in a token in the user home. As long as you are working online, the token is refreshed every hour. The local cache will keep working in offline mode until that token expires after 24 hours.

Configuring the remote cache

Gradle Enterprise provides a cache node that is built into the server. Additionally, remote cache nodes can be spun up and connected to the server. By default, the built-in cache node of the Gradle Enterprise server is used.

Using a different cache node

The address of the remote cache node can be configured in the server option.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <server>
        <url>http://my-node/cache/</url>
      </server>
    </remote>
  </buildCache>
</gradleEnterprise>

Note that you still need to configure the address of your Gradle Enterprise server in the top-level server option.

Similar to the top-level Gradle Enterprise server configuration, the remote cache server configuration also provides an allowUntrusted option to circumvent certificate warnings:

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <server>
        <url>http://my-node/cache/</url>
        <allowUntrusted>true</allowUntrusted>
      </server>
    </remote>
  </buildCache>
</gradleEnterprise>

In order to use an authenticated cache node the credentials for that node have to be configured in the settings.xml file. You can also use Maven’s password encryption feature to safely store these credentials.

<user-home>/.m2/settings.xml
<servers>
  <server>
    <id>my-node</id>
    <username>username</username>
    <password>password</password>
  </server>
</servers>

The cache node with the ID my-node can then be referenced in the remote cache configuration.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <server>
        <id>my-node</id>
        <url>http://my-node/cache/</url>
      </server>
    </remote>
  </buildCache>
</gradleEnterprise>
Disabling the remote cache

The remote build cache is enabled by default. This can be changed by setting the enabled option to false.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <enabled>false</enabled>
    </remote>
  </buildCache>
</gradleEnterprise>

You can also use the -Dgradle.cache.remote.enabled=false system property to achieve the same effect.

Enabling remote store

Since the remote build cache is shared with other developers and CI machines, storing in the remote cache is disabled by default. Storing outputs in the remote cache can be enabled by setting the storeEnabled option to true.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <storeEnabled>true</storeEnabled>
    </remote>
  </buildCache>
</gradleEnterprise>

You can also use the -Dgradle.cache.remote.storeEnabled=true system property to achieve the same effect.

In general, the remote cache should only be populated by controlled build environments such as CI servers. Therefore, the recommendation is to only enable it on the CI server.

Normalization

Your classpaths may contain files that are not relevant for running or testing your code. A typical example are property files containing the current time. If left unchecked, such property files trigger a rerun of your tests on every build because the extension needs to assume that your code is making decision based on the contents of these files. The following snippet shows you how to ignore any file called META-INF/build.properties on any runtime classpath in the given project. You can share this setting across many projects by putting it in the pluginManagement section of your parent POM. You can use ANT-style patterns like META-INF/*/.sql as well.

<plugin>
  <groupId>com.gradle</groupId>
  <artifactId>gradle-enterprise-maven-extension</artifactId>
  <version>1.0.4</version>
  <configuration>
    <gradleEnterprise>
      <normalization>
        <runtimeClassPath>
          <ignoredFiles>
            <ignoredFile>META-INF/build.properties</ignoredFile>
          </ignoredFiles>
        </runtimeClassPath>
      </normalization>
    </gradleEnterprise>
  </configuration>
</plugin>

Disabling build caching for a plugin or execution

You can disable caching on a fine-grained level in the <pluginManagement> section of your pom.xml file. The following will disable caching for all executions of the failsafe plugin in the given project:

<plugin>
  <groupId>com.gradle</groupId>
  <artifactId>gradle-enterprise-maven-extension</artifactId>
  <version>1.0.4</version>
  <configuration>
    <gradleEnterprise>
      <plugins>
        <plugin>
          <artifactId>maven-failsafe-plugin</artifactId>
          <outputs>
            <notCacheableBecause>these tests verify integration with other systems and should rerun even if our inputs didn't change</notCacheableBecause>
          </outputs>
        </plugin>
      </plugins>
    </gradleEnterprise>
  </configuration>
</plugin>

You can also disable caching for a specific execution. Other executions of that plugin will then still remain cacheable. The following will disable caching only for the default-compile execution of the compiler plugin. Test compilation would still be cacheable.

<plugin>
  <groupId>com.gradle</groupId>
  <artifactId>gradle-enterprise-maven-extension</artifactId>
  <version>1.0.4</version>
  <configuration>
    <gradleEnterprise>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <executions>
            <execution>
              <id>default-compile</id>
              <outputs>
                <notCacheableBecause>the ORM annotation processor creates files under target/schema/sql, which is not tracked as an output</notCacheableBecause>
              </outputs>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </gradleEnterprise>
  </configuration>
</plugin>

Solving problems with build caching

While working with the build cache you may encounter situations where build results are not retrieved from cache although you would expect them to. This section provides guidance for analyzing and solving these problems.

Debugging cache operations

The extension provides several loggers to make analyzing problems with build caching easier. To show the effective cache configuration, use the gradle.goal.cache logger:

$ mvn clean verify -Dorg.slf4j.simpleLogger.log.gradle.goal.cache=debug
[DEBUG] Using the build cache with the following configuration:
  Local build cache: enabled
      directory: /Users/johndoe/.m2/.gradle-enterprise/build-cache
      cleanup: enabled
          retention: 168h
          interval: 24h
  Remote build cache: enabled
      url: https://my-server/cache/
      authenticated: false
      storeEnabled: false
      allowUntrustedServer: false

The gradle.goal.cache logger will also print the result of determining the cacheability of the executed goals:

[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-build-scan-extension-sample ---
[INFO] skip non existing resourceDirectory /Users/johndoe/workspace/maven-build-scan-quickstart/src/main/resources
[DEBUG] Build caching was not enabled for this goal execution because the 'resources' goal was not supported.
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-build-scan-extension-sample ---
[DEBUG] Local cache miss
[DEBUG] Remote cache miss
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/johndoe/workspace/maven-build-scan-quickstart/target/classes
[DEBUG] Stored outputs in the local build cache

All information printed by the gradle.goal.cache can also be viewed in the build scan for that build.

Finding the cause of cache misses

Sometimes you might encounter a situation where a goal execution is not avoided by using the cache although you would expect it to be. For example, if you run the same build twice without any changes, the outputs of all supported goals should be retrieved from the local build cache (if it is enabled). If this is not the case, this almost always is caused by unstable inputs, e.g. a timestamp being added to a file by some build logic. In order to identify which inputs change between builds the gradle.goal.fingerprint logger can be used. To find the changing inputs its best to write the log outputs to files and then compare them using the diff tool:

mvn -Dorg.slf4j.simpleLogger.log.gradle.goal.fingerprint=trace clean verify | tee log1.txt
mvn -Dorg.slf4j.simpleLogger.log.gradle.goal.fingerprint=trace clean verify | tee log2.txt

In this example, the build was configured to write a timestamp to the build.properties file. When comparing the two log files using diff the suspicious file shows up with different fingerprints, resulting in different cache keys:

diff log1.txt log2.txt
67c67
< [TRACE] Cache key: a7dafb9be698e4b2c3978690cf9a1853
---
> [TRACE] Cache key: 22822d129631bde1bef50f3bb8dc1338
97,98c97,98
< [TRACE] Fingerprint for input file property classesDirectory, using CLASSPATH strategy: 534e939040586d21febbc60f263159e6
< [TRACE] - /Users/johndoe/workspace/maven-cache-changing-input-example/target/classes/build.properties (normalized to 'build.properties'): abdb0f3a64d2dc7ca14752b3b6ff5875
---
> [TRACE] Fingerprint for input file property classesDirectory, using CLASSPATH strategy: 82e047d840f88737d2fd0b45ff09c921
> [TRACE] - /Users/johndoe/workspace/maven-cache-changing-input-example/target/classes/build.properties (normalized to 'build.properties'): 66f666ebbbda85cf856c6473ee9e4f3d

Once the changing input is identified, the build can be changed to be reproducable or normalization can be used to ignore the changing input.

Solving common causes of cache misses

Some widely used Maven plugins are a common cause of cache misses because they produce changing build results. This chapter shows you how to solve them.

JAXB

Old versions of the XJC binding compiler generate classes with methods in random order on each invocation. This has been fixed in JAXB 2.2.11. Since there are several Maven plugins available for JAXB, you need to find out which release of the plugin you are using includes the fixed JAXB release. For example the jaxb2-maven-plugin includes the fix starting from release 2.1.

Another cause of unstable build results when using JAXB is the fact that the XJC binding compiler generates a header containing a timestamp into all Java classes. This behavior is controlled by the --no-header option which is false by default (= always generate a header). To prevent this add the corresponding configuration to the Maven plugin you use, for example:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>jaxb2-maven-plugin</artifactId>
  <version>2.4</version>
  <configuration>
    <noGeneratedHeaderComments>true</noGeneratedHeaderComments>
  </configuration>
</plugin>
maven-bundle-plugin

By default the maven-bundle-plugin generates a timestamp the MANIFEST.MF file. To prevent this, the configuration of the plugin has to be adjusted:

<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <configuration>
    <archive>
      <addMavenDescriptor>false</addMavenDescriptor>
    </archive>
    <instructions>
      <_removeheaders>Bnd-LastModified</_removeheaders>
    </instructions>
  </configuration>
</plugin>

Note that the underscope in <_removeheaders> is not a typo.

maven-resources-plugin

A common pattern is to write a build timestamp to a build.properties file using Maven resource filtering. One way to fix this is using Normalization to ignore the file. Alternatively the build can be adjusted by moving the generation of the timestamp to a separate profile that is only executed when creating a release:

build.properties
build.timestamp=${timestamp}
pom.xml
<properties>
  <timestamp>2019-03-07 12:00:00.000</timestamp>
</properties>

<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <filtering>true</filtering>
    </resource>
  </resources>
</build>

<profile>
  <id>release</id>
  <properties>
    <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss.S</maven.build.timestamp.format>
    <timestamp>${maven.build.timestamp}</timestamp>
  </properties>
</profile>

Troubleshooting

Slow build startup in MacOS Sierra after applying Gradle Enterprise Maven extension

An issue with resolving localhost affecting MacOS Sierra can cause a 5 second delay after applying the Gradle Enterprise Maven extension to your build. Until the issue is fixed, there is a workaround that you can apply to your /etc/hosts file. Add these lines to your /etc/hosts file, substituting your computer name for 'mbpro' in the below snippet:

/etc/hosts
127.0.0.1   localhost mbpro.local
::1         localhost mbpro.local

Appendix A: Configuration reference

gradle-enterprise.xml

Most aspects of the Gradle Enterprise Maven extension are configured in the gradle-enterprise.xml configuration file. Some of the options can be overwritten by system properties. However, once you specify a value in a gradle-enterprise.xml file, the corresponding system property will be ignored.

The gradle-enterprise.xml file can be put into several locations. These files are merged and their properties overwritten based on the precedence rules below:

  • <maven-home>/conf/gradle-enterprise.xml is used to set global defaults for a given Maven installation. This is useful when you ship a custom Maven distribution to your teams. The location of this configuration file can be overwritten using the -Dgradle.global.config argument. This can be useful for CI environments where changing the Maven installation is not possible.

  • <project-dir>/.mvn/gradle-enterprise.xml is used for project-specific configuration and overrides the global configuration.

  • <user-home>/.m2/gradle-enterprise.xml is used for user-specific configuration and overrides the project configuration. The location of this configuration file can be overwritten using the -Dgradle.user.config argument. This can be useful for CI environments where changing the user home is not possible.

The example below shows a full reference of everything you can configure in this file.

Be sure to include the XML namespace declarations to get auto-completion in your IDE. You can get a specific schema version by appending the version to the schema location, e.g. https://www.gradle.com/schema/gradle-enterprise-maven-1.0.xsd.

gradle-enterprise.xml
<gradleEnterprise
    xmlns="https://www.gradle.com/gradle-enterprise-maven" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://www.gradle.com/gradle-enterprise-maven https://www.gradle.com/schema/gradle-enterprise-maven.xsd">
  <server>
    <!-- ID used to reference an element in the settings.xml -->
    <id>my-server</id>
    <!-- Address of the Gradle Enterprise server. System property is 'gradle.enterprise.url'. -->
    <url>http://my-server/</url>
    <!-- Whether untrusted connections to the Gradle Enterprise server should be accepted. Defaults to false. -->
    <allowUntrusted>false</allowUntrusted>
  </server>
  <buildScan>
    <!-- Behavior of publishing build scans. Possible values are ALWAYS, ON_FAILURE, ON_DEMAND. Defaults to ALWAYS. -->
    <publish>ALWAYS</publish>
    <!-- Terms of service acceptance (mandatory to publish to scans.gradle.com) -->
    <termsOfService>
      <!-- Address of the terms of service. Must be 'https://gradle.com/terms-of-service'. Defaults to an empty string. -->
      <url></url>
      <!-- Signal acceptance of the terms of service. Must be 'true'. Defaults to false. -->
      <accept>false</accept>
    </termsOfService>
    <!-- List of tags to capture -->
    <tags>
      <tag>my tag</tag>
    </tags>
    <!-- List of links to capture -->
    <links>
      <link>
        <name>my link</name>
        <url>http://my-site.com</url>
      </link>
    </links>
    <!-- List of custom values to capture -->
    <values>
      <value>
        <name>my name</name>
        <value>my value</value>
      </value>
    </values>
  </buildScan>
  <buildCache>
    <!-- Local cache configuration -->
    <local>
      <!-- Whether the local cache is enabled. Defaults to true. System property is 'gradle.cache.local.enabled'. -->
      <enabled>true</enabled>
      <!-- Local cache directory. Defaults to ${user.home}/.m2/.gradle-enterprise/build-cache. System property is 'gradle.cache.local.directory'. -->
      <directory>/some/other/location</directory>
      <!-- Local cache cleanup configuration -->
      <cleanup>
        <!-- Whether local cache cleanup is enabled. Defaults to true. System property is 'gradle.cache.local.cleanup.enabled'. -->
        <enabled>true</enabled>
        <!-- Items in the cache that were not used in this period will be deleted. Defaults to P7D. System property is 'gradle.cache.local.cleanup.retention'. -->
        <retention>P30D</retention>
        <!-- Interval at which the cleanup occurs. Defaults to P1D. System property is 'gradle.cache.local.cleanup.interval'. -->
        <interval>P10D</interval>
      </cleanup>
    </local>
    <!-- Remote cache configuration -->
    <remote>
      <!-- Remote cache server configuration -->
      <server>
        <!-- Optionally use the ID of a server specified in your settings.xml to use its credentials. System property is 'gradle.cache.remote.serverId'. -->
        <id>remote-cache</id>
        <!-- URL of the remote cache. Defaults to ${gradle.enterprise.url}/cache/. System property is 'gradle.cache.remote.url'. -->
        <url>http://my-node/cache/</url>
        <!-- Whether the remote cache accepts untrusted connections. Defaults to false. System property is 'gradle.cache.remote.allowUntrustedServer'. -->
        <allowUntrusted>true</allowUntrusted>
      </server>
      <!-- Whether the remote cache is enabled. Defaults to true. System property is 'gradle.cache.remote.enabled'. -->
      <enabled>true</enabled>
      <!-- Whether to store outputs in the remote build cache (as opposed to only loading from it). Defaults to false. System property is 'gradle.cache.remote.storeEnabled'. -->
      <storeEnabled>true</storeEnabled>
    </remote>
  </buildCache>
</gradleEnterprise>

In order to use an authenticated Gradle Enterprise server, the credentials for that server have to be configured in the settings.xml file. You can also use Maven’s password encryption feature to safely store these credentials.

<user-home>/.m2/settings.xml
<servers>
  <server>
    <id>my-server</id>
    <username>username</username>
    <password>password</password>
  </server>
</servers>

pom.xml

The Gradle Enterprise Maven extension also allows you to configure module-specific aspects in the corresponding pom.xml file. This allows you to share common configuration between your project by putting it in a parent POM. See the example below for a full reference.

In order to get auto-completion in your IDE, be sure to include the XML namespace and schema location as shown in the example below. You can get a specific schema version by appending the version to the schema location, e.g. https://www.gradle.com/schema/gradle-enterprise-maven-project-1.0.xsd. Please note that this is currently only supported by Eclipse. For IntelliJ IDEA, there’s an open issue to add such a feature.

pom.xml
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd
        https://www.gradle.com/gradle-enterprise-maven-project https://www.gradle.com/schema/gradle-enterprise-maven-project.xsd">

...

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <version>1.0.4</version>
      <configuration>
        <gradleEnterprise xmlns="https://www.gradle.com/gradle-enterprise-maven-project">
          <normalization>
            <runtimeClassPath>
              <ignoredFiles>
                <ignoredFile>META-INF/build.properties</ignoredFile>
              </ignoredFiles>
            </runtimeClassPath>
          </normalization>
          <plugins>
            <plugin>
              <artifactId>maven-failsafe-plugin</artifactId>
              <outputs>
                <notCacheableBecause>these tests verify integration with other systems and should rerun even if our inputs didn't change</notCacheableBecause>
              </outputs>
            </plugin>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <executions>
                <execution>
                  <id>default-compile</id>
                  <outputs>
                    <notCacheableBecause>the ORM annotation processor creates files under target/schema/sql, which is not tracked as an output</notCacheableBecause>
                  </outputs>
                </execution>
              </executions>
            </plugin>
          </plugins>
          <buildScan>
            <tags>
              <tag>my tag</tag>
            </tags>
            <links>
              <link>
                <name>my link</name>
                <url>http://my-site.com</url>
              </link>
            </links>
            <values>
              <value>
                <name>Build Number</name>
                <value>${project.buildNumber}</value>
              </value>
            </values>
          </buildScan>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

Appendix B: API reference

Please see the Javadoc.

Appendix C: Build scans captured information

The Gradle Enterprise Maven extension captures information while the build is running and transmits it to a server after the build has completed.

Most of the information captured can be considered to be build data. This includes the name of the projects in your build, the executed goals, plugins and other things of this nature. Some more general environmental information is also captured. This includes your Java version, operating system, hardware, country, timezone and other things of this nature.

Notably, the actual source code being built and the output artifacts are not captured. However, error messages emitted by compilers or errors in tests may reveal aspects of the source code.

Listing

The list below details the notable information captured by the Gradle Enterprise Maven extension and transmitted in a build scan.

  • Environment

    • Build Java Virtual Machine

    • Username (system property 'user.name')

    • Local hostname (environment variable 'COMPUTERNAME' / 'HOSTNAME')

    • Public hostname

    • IP addresses

    • Operating System

    • Hardware

  • Build

    • Maven command-line and invocation options (e.g. requested phases and goals, switches)

    • Build failure exception messages and stacktraces

    • Projects

    • Executed goals

    • Applied plugins

Access

Build scans published to a Gradle Enterprise installation are viewable by all users that can reach the server and have the required roles, should Identity Access Management (IAM) be turned on. Gradle Enterprise provides a search interface for discovering and finding individual build scans.

Build scans published to scans.gradle.com are viewable by anyone with the link assigned when publishing the build scan. Links to individual build scans are not discoverable and cannot be guessed, but may be shared.

Appendix D: Cacheable plugins and goals

The extension caches the following plugins and goals out of the box. Unless otherwise noted, all their parameters are tracked as part of the cache key.

maven-compiler-plugin

Supported versions: 3.1 and above

Supported goals:

  • compile

  • testCompile

Caching is automatically disabled if:

  • a non-javac toolchain is used

The following use cases currently require disabling the cache for this plugin:

  • using annotation processors that read files outside of Maven’s resource directories

  • using annotation processors that generate sources outside of Maven’s generated sources directory

  • using any non-deterministic annotation processors

Compile avoidance

The Java compiler only considers the signatures of the classes on the classpath. The extension uses this knowledge to avoid recompiling your sources when only an implementation detail on the classpath has changed.

However, if there are annotation processors on the classpath, Maven needs to consider all implementation details, because annotation processors are executed during compilation. This disables compile avoidance and lowers your cache hit ratio. The extension will detect this and issue a build warning:

[WARNING] The following annotation processors were found on the classpath: [com.acme.SomeAnnotationProcessor].
This significantly reduces the cache hit ratio. Please use the <annotationProcessorPaths> configuration element of the compiler plugin to declare the processors instead.
If you did not intend to use the processors above (e.g. they were leaked by a dependency), you can use the <proc>none</proc> option to disable annotation processing.

To fix this, please declare your annotation processors explicitly using the compiler plugin’s <annotationProcessorPaths> configuration. If you don’t want to use annotation processors at all (and they are only on your classpath by accident), you can use the <proc>none</proc> option to tell the compiler and the extension that these processors should be ignored.

maven-surefire-plugin and maven-failsafe-plugin

Supported versions: 2.12.4 and above

Supported goals:

  • surefire:test

  • failsafe:integration-test

Caching is automatically disabled if:

The following use cases currently require disabling the cache for these plugins:

  • non-deterministic tests (e.g. tests with random parameters)

  • tests that read files that are not on the test classpath (e.g. new File("src/test/samples"))

  • tests that write additional results that you absolutely need (e.g. screenshots for failed UI tests)

  • tests that read environment variables that are not explicitly declared using the <environmentVariables> property

  • tests that use Java agents that read additional inputs or create additional outputs, except for JaCoCo, which is explicitly supported

The following properties are deliberately not tracked as inputs, because they should not influence the test result:

jacoco-maven-plugin

Supported versions: 0.5 and above

Supported goals:

  • none of JaCoCo’s own goals are cached

  • surefire and failsafe remain cacheable when JaCoCo is used

Caching is automatically disabled if:

The JaCoCo plugin hooks into surefire and failsafe as a Java agent. The extension automatically tracks all JaCoCo agent options when determining the cache key for surefire and failsafe tests. The JaCoCo execution data file is cached as an additional output of the test execution.

Appendix E: Anatomy of the .gradle-enterprise directory

By default, the Gradle Enterprise Maven extension stores temporary data in the ${user.home}/.m2/.gradle-enterprise directory. The location can be customized by setting the gradle.enterprise.storage.directory system property:

$ mvn clean verify -Dgradle.enterprise.storage.directory=/path/to/.gradle-enterprise

The directory may contain the following subdirectories:

build-cache

Location of the local build cache

build-cache-tmp

Temporary directory for loading and storing entries in the remote build cache in case the local build cache is disabled

build-scan-data

Data collected to create build scans

token-cache

Location of the entitlement tokens

The .gradle-enterprise directory is an internal directory and subject to change without warning.

Appendix F: Gradle Enterprise Maven Extension release history

1.0.4 - 15th March 2019
Fix ID generation for build cache events.
Jar fingerprints are stored in the build cache.

1.0.3 - 12th March 2019
Fix hashing of build cache operations, that was leading to event ID collisions.
Support a proxy server when publishing build scans.
Prevent publishing build scans when there is no entitlement.

1.0.2 - 6th March 2019
Runtime classpath normalization recursively inspects WAR, EAR, ZIP and APK files.
Track runOrder as input property for the maven-surefire-plugin and maven-failsafe-plugin.
Fix project capturing to respect <module> declarations.

1.0.1 - 1st March 2019
Restore compatibility with Maven < 3.5.2.
Enhance console output of terms of services.

1.0 - 28th February 2019
Initial Release.

Appendix G: Gradle Enterprise Maven Extension compatibility with Apache Maven and Gradle Enterprise

Compatibility between versions of Apache Maven, Gradle Enterprise, and the Gradle Enterprise Maven extension can be found here.