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, build cache acceleration, and test distribution.

Getting set up

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.

Add the following to .mvn/extensions.xml
<extensions>
  <extension>
    <groupId>com.gradle</groupId>
    <artifactId>gradle-enterprise-maven-extension</artifactId>
    <version>1.10.2</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. Similarly to the extensions.xml file, the gradle-enterprise.xml file can be placed in your Maven project’s .mvn directory. Unless you intend to publish build scans to scans.gradle.com, the minimum configuration required is that of the Gradle Enterprise server URL.

Add the following to .mvn/gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <url>https://gradle-enterprise.mycompany.com</url>
  </server>
</gradleEnterprise>

Other locations for the gradle-enterprise.xml file are possible. For the full reference of the extension’s configuration, see the configuration reference. All available configuration options will be introduced over the coming sections.

Since version 1.2, the Gradle Enterprise Maven extension captures an identifier used to uniquely represent a given workspace.

  • For versions 1.2.3+, this identifier is stored under .mvn/.gradle-enterprise/gradle-enterprise-workspace-id. The .mvn/.gradle-enterprise folder should NOT be committed under version control.

  • For versions [1.2 - 1.2.2], the identifier is stored under .mvn/gradle-enterprise-workspace-id.txt. The file is migrated to the new location when upgrading to a Gradle Enterprise Maven extension version that is at least 1.2.3. The .mvn/gradle-enterprise-workspace-id.txt file should NOT be committed under version control.

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.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setServer("https://gradle-enterprise.mycompany.com");
$ mvn package -Dgradle.enterprise.url=https://gradle-enterprise.mycompany.com
Add the following to 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:

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setAllowUntrusted(true);
Allowing untrusted certificate cannot be done via command-line argument.
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <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.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setTermsOfServiceUrl("https://gradle.com/terms-of-service");
buildScan.setTermsOfServiceAgree("true");
Accepting the terms of service cannot be done via command-line argument.
Add the following to 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 prompted 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.

Integrating your CI tool

The Gradle plugin for Jenkins prominently displays links to the build scan for any Maven builds that produce build scans. This makes viewing the build scan of CI builds much easier.

jenkins

A TeamCity build scan plugin is also available that provides a prominent link to the build scan for executed builds.

teamcity

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. Should you decide to explicitly enforce this default option, you can do this as follows:

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.publishAlways();
Always publishing a build scan cannot be done via command-line argument.
Add the following to 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.

If you want to deactivate build scans for a particular build, you can pass the -Dscan=false system property to Maven.

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:

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.publishOnDemand();
-Dscan

For instance:
$ mvn clean verify -Dscan
Add the following to 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

Publishing based on criteria

Many of you will want a bit more control over exactly when build scans are published without resorting to using -Dscan each time. Perhaps you only want to publish build scans when the build fails, or if the build is running on your continuous integration infrastructure. Such scenarios are covered by the options in the following table.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.publishAlwaysIf(true); // Publish a build scan if the given condition is true, regardless of whether the build succeeds or fails
buildScan.publishOnFailure(); // Publish a build scan only when the build fails
buildScan.publishOnFailureIf(true); // Publish a build scan only if the condition is true and the build fails
Conditional publication is not available via command-line argument.
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <publish>ON_FAILURE</publish>
  </buildScan>
</gradleEnterprise>

Giving a more concrete example, let’s say you only want to publish build scans from your CI system, which is identified by having a CI environment variable. This configuration will do the trick:

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.publishAlwaysIf(System.getenv("CI") != null);
Conditional publication is not available via command-line argument.
<gradleEnterprise>
  <buildScan>
    <publish>#{env['CI'] == null ? 'ON_DEMAND' : 'ALWAYS'}</publish>
  </buildScan>
</gradleEnterprise>

Configuring background uploading

(Maven extension 1.5+, Gradle Enterprise 2020.2+)

By default, build scans are uploaded in the background after the build has finished. This allows the build to finish sooner, but can be problematic in build environments (e.g. ephemeral CI agents) that terminate as soon as the build is finished, as the upload may be terminated before it completes. Background uploading should be disabled for such environments.

Prior to version 1.5 of the Maven extension, build scans are always uploaded before the build finishes.

Background build scan upload can be disabled programmatically, via a system property, or via the gradle-enterprise.xml configuration file.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setUploadInBackground(false);
-Dgradle.scan.uploadInBackground=false

For instance:
$ mvn clean verify -Dgradle.scan.uploadInBackground=false
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <backgroundBuildScanUpload>false</backgroundBuildScanUpload>
  </buildScan>
</gradleEnterprise>

It may be desirable to conditionally set the value based on the environment.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setUploadInBackground(System.getenv("CI") == null);
-Dgradle.scan.uploadInBackground=false

Add the system property only on the CI build configuration:
$ mvn clean verify -Dgradle.scan.uploadInBackground=false
<gradleEnterprise>
  <buildScan>
    <backgroundBuildScanUpload>#{env['CI'] == null}</backgroundBuildScanUpload>
  </buildScan>
</gradleEnterprise>

Authenticating with Gradle Enterprise

(Maven extension 1.3+, Gradle Enterprise 2019.4+)

Gradle Enterprise installations may be configured to require build scan publishing to be authenticated. Additionally, installations may be configured to only allow certain users to publish build scans.

Gradle Enterprise access keys should be treated with the same secrecy as passwords. They are used to authorize access to Gradle Enterprise from a build.

Automated access key provisioning

The easiest way to configure a build environment to authenticate with Gradle Enterprise is to use the the following goal:

$ mvn com.gradle:gradle-enterprise-maven-extension:1.10.2:provision-access-key

When executed, it opens your web browser and asks to confirm provisioning of a new access key. You will be asked to sign in to Gradle Enterprise in your browser first if you are not already signed in.

When confirmed, a new access key will be generated and stored in the .gradle-enterprise/keys.properties file within the Maven user home directory (~/.m2 by default).

Any existing access key for the same server will be replaced in the file, but will not be revoked at the server for use elsewhere. To revoke old access keys, sign in to Gradle Enterprise and access “My settings” via the user menu at the top right of the page.

If your browser cannot be opened automatically at the correct page, you will be asked to manually open a link provided in the build console.

Manual access key configuration

Access keys can also be configured manually for an environment, when automated provisioning is not suitable.

Creating access keys

To create a new access key, sign in to Gradle Enterprise and access “My settings” via the user menu at the top right of the page. From there, use the “Access keys” section to generate an access key.

The access key value should then be copied and configured in your build environment via file or via environment variable.

Via file

Gradle Enterprise access keys are stored inside the Maven user home directory (~/.m2 by default), at .gradle-enterprise/keys.properties, in a Java properties file. The property name refers to the host name of the server, and the value is the access key.

gradle-enterprise.mycompany.com=7w5kbqqjea4vonghohvuyra5bnvszop4asbqee3m3sm6dbjdudtq

The file may contain multiple entries. The first entry for a given host value will be used.

Via environment variable

The access key may also be specified via the GRADLE_ENTERPRISE_ACCESS_KEY environment variable. This is typically more suitable for CI build environments.

The environment variable value format is «server host name»=«access key».

$ export GRADLE_ENTERPRISE_ACCESS_KEY=gradle-enterprise.mycompany.com=7w5kbqqjea4vonghohvuyra5bnvszop4asbqee3m3sm6dbjdudtq
$ mvn package

The server host name is specified in order to prevent the access key being transmitted to a different server than intended. In the rare case that you require access keys for multiple servers, you can specify multiple entries separated by semicolons (requires Maven extension 1.9+, Gradle Enterprise 2021.1+).

$ export GRADLE_ENTERPRISE_ACCESS_KEY=ge1.mycompany.com=7w5kbqqjea4vonghohvuyra5bnvszop4asbqee3m3sm6dbjdudtq;ge2.mycompany.com=9y4agfiubqqjea4vonghohvuyra5bnvszop4asbqee3m3sm67w5k
$ ./gradlew build

Capturing goal input files

(Maven extension v1.1+)

Build scans capture hashes of goal inputs, to enable identifying changes to inputs when comparing builds, among other features. By default, an overall hash value for each goal input property is captured. This enables identifying which properties changed for a goal execution (e.g. the source or the classpath for Java compilation), but not which individual files changed. In order to identify file changes, the paths and content hashes of each individual input file must be captured, which can be enabled.

When to enable

Capturing goal input files increases the amount of data transmitted to the build scan server at the end of the build. If the network connection to the build scan server is poor, it may increase the time required to transmit. Additionally, it may also increase the data storage requirements for the build scan server.

This data is currently only used for build comparison, which is only available in Gradle Enterprise and is not available with scans.gradle.com. If you are using scans.gradle.com, it is not recommended that you enable capture of goal input files.

If you are using Gradle Enterprise and utilising its build cache to accelerate your builds, it is strongly recommended to enable capture of goal input files as identifying which files have changed between builds with build comparison is extremely effective for diagnosing unexpected build cache misses.

How to enable

Goal input files capture can be enabled programmatically, via a system property, or via the gradle-enterprise.xml configuration file.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setCaptureGoalInputFiles(true);
-Dgradle.scan.captureGoalInputFiles

For instance:
$ mvn clean verify -Dgradle.scan.captureGoalInputFiles=true
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <captureGoalInputFiles>true</captureGoalInputFiles>
  </buildScan>
</gradleEnterprise>

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.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.tag("my tag");
-Dscan.tag.<tag>

For instance:
$ mvn package -Dscan.tag.CI
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <tags>
      <tag>my tag</tag>
    </tags>
  </buildScan>
</gradleEnterprise>
Add the following to a project pom.xml
<project>
  <!-- other build configuration -->
  <pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <buildScan>
            <tags>
              <tag>my tag</tag>
            </tags>
          </buildScan>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
  </pluginManagement>
</project>

Prefer the programmatic access.
If specified in parent POM, tags will be applied to all projects inheriting from it.

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.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.link("my link", "http://my-site.com");
-Dscan.link.<name>=<URL>

For instance:
$ mvn package -Dscan.link.VCS=https://github.com/myorg/my-super-project/tree/my-new-feature
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <links>
      <link>
        <name>my link</name>
        <url>http://my-site.com</url>
      </link>
    </links>
  </buildScan>
</gradleEnterprise>
Add the following to a project pom.xml
<project>
  ...
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>com.gradle</groupId>
        <artifactId>gradle-enterprise-maven-extension</artifactId>
        <configuration>
          <gradleEnterprise>
            <buildScan>
              <links>
                <link>
                  <name>my link</name>
                  <url>http://my-site.com</url>
                </link>
              </links>
            </buildScan>
          </gradleEnterprise>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</project>

Prefer the programmatic access.
If specified in parent POM, links will be applied to all projects inheriting from it.

Links can also be added in your project POM.

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.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.value("my name", "my value");
-Dscan.value.<name>=<value>

For instance:
$ mvn package "-Dscan.value.CIBuildType=QA_Build"
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildScan>
    <values>
      <value>
        <name>my name</name>
        <value>my value</value>
      </value>
    </values>
  </buildScan>
</gradleEnterprise>
Add the following to a project pom.xml
<project>
  ...
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>com.gradle</groupId>
        <artifactId>gradle-enterprise-maven-extension</artifactId>
        <configuration>
          <gradleEnterprise>
            <buildScan>
              <values>
                <value>
                  <name>Build Number</name>
                  <value>${project.buildNumber}</value>
                </value>
              </values>
            </buildScan>
          </gradleEnterprise>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</project>

Prefer the programmatic access.
If specified in parent POM, custom values will be applied to all projects inheriting from it.

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

Callbacks

The Build Scan API allows to programmatically interact with the build scan configuration of the Gradle Enterprise Maven extension. Please see the Javadoc for the complete API documentation.

Executing operations at the end of the build

(Maven extension v1.2+)

What if you want to execute some code based on data that is only available late in the build? For example, you might want to label a build as "built-from-clean" if the clean goal was run. But you don’t know if that’s the case until the goal execution plan is ready.

The Maven extension provides a buildFinished() hook that you can use for these situations. It defers attaching custom data until the build has finished running. As an example, imagine you want to report how much disk space was taken up by the output directory. The build doesn’t know this until it’s finished, so the solution is to calculate the disk space and attach it to a custom value in the buildFinished() hook:

import org.apache.commons.io.FileUtils;
import java.io.File;
...
BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
File outputDir = mavenSession.getCurrentProject().getBuild().getOutputDirectory();
buildScan.buildFinished(buildResult -> buildScan.value("Disk usage (target dir)", FileUtils.sizeOfDirectory(outputDir)));
Adding data at the end of the build is not available via command-line argument.
Adding data at the end of the build is not available via XML configuration.

The buildFinished() action has access to a BuildResult instance that you can use to determine whether the build failed or not, like so:

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.buildFinished(buildResult -> {
  buildResult.getFailures().stream().forEach(failure ->
    buildScan.value("Failed with", failure.getMessage());
  );
});
Adding data at the end of the build is not available via command-line argument.
Adding data at the end of the build is not available via XML configuration.

Executing operations when a build scan is published

(Maven extension v1.2+)

You might want to perform a custom operation when a build scan is published, like notifying an internal tool of your company. To do this , you can use the BuildScanApi#buildScanPublished() method:

import java.nio.Files
...
BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
File journal = new File("buildScans.txt")
buildScan.buildScanPublished(buildScan -> Files.write(journal.toPath(), buildScan.getBuildScanId().getBytes(), StandardOpenOption.APPEND));
Executing operations when a build scan is published cannot be configured via command-line argument.
Executing operations when a build scan is published cannot be configured via XML.

Executing expensive operations

(Maven extension v1.2+)

Some data that you may wish to add to your build scan can be expensive to capture. For example, capturing the Git commit ID may require executing the git command as an external process, which is expensive. To do this without slowing your build down, you can use the BuildScanApi#background() method:

import java.io.File;
import org.eclipse.jgit.*;
...
BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.background(api -> {
  File projectDir = mavenSession.getCurrentProject().getBasedir();
  Git git = Git.open(projectDir);
  ObjectId objectId = git.getRepository().resolve("HEAD");
  api.value("Git Commit ID", ObjectId.toString(objectId));
});
Doing expensive work in the background cannot be configured via command-line argument.
Doing expensive work in the background cannot be configured via XML.

This method takes a function that will be executed on a separate thread, which allows Maven to continue without waiting for the expensive work to complete.

All background work will be completed before finishing the build and publishing the build scan.

Any errors that are thrown by the background action will be logged and captured in the build scan.

See the BuildScanApi#background() API reference for more information.

Executing operations only once

(Maven extension v1.2.3+)

You may want to ensure that a given operation is only executed once for the whole execution of a multi-project Maven build.

The Maven extension provides an executeOnce() hook that you can use for these situations. It must be provided with an identifier, and can call any service provided by the API. The identifier is used to guarantee that the provided action will be executed at most once.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.executeOnce("capture custom data", api -> api.tag("my custom tag")); // This will be executed once
buildScan.executeOnce("capture custom data", api -> api.tag("my other custom tag")); // This will not be executed and will silently be ignored
buildScan.executeOnce("publish to journal", api -> api.buildScanPublished(buildScan -> journalServer.add(buildScan))); // This will be executed once
Running custom build scan configuration logic once is not available via command-line argument.
Running custom build scan configuration logic once is not available via XML configuration.

Obfuscating identifying data

(Maven extension v1.3.1+)

Build scans capture certain identifying information such as the operating system username, hostname and network addresses. You may choose to obfuscate this data so that it is not decipherable in build scans when viewed. To do this, you can use the BuildScanApi#obfuscation() method.

With Maven extension v1.6.3+, you can register obfuscation values on the gradle-enterprise.xml. These will always be applied, even in case of very early build failures, that would prevent programmatic configuration via the BuildScan API. If the BuildScan API gets eventually called, obfuscation functions registered with it will have precedence over the values defined via XML.

The following examples show registering obfuscation functions for the different identifying data.

Obfuscating the username using the Programmatic configuration
import java.util.stream.Collectors;
...
BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.obfuscation(obfuscation -> obfuscation.username(s -> s.chars().mapToObj(c -> String.valueOf(Character.getNumericValue(c))).collect(Collectors.joining())));
Obfuscating the username cannot be configured via command-line argument.
Obfuscating the username via gradle-enterprise.xml (Maven extension v1.6.3+)
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use a redacted value. -->
      <username>obfuscated</username>
    </obfuscation>
  </buildScan>
</gradleEnterprise>
---
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use Spring Expression Language. The 'username' variable can be used to get access to the username that would be captured. -->
      <username>#{username.substring(0,1)}</username>
    </obfuscation>
  </buildScan>
</gradleEnterprise>
---
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use Spring Expression Language. The 'sha512' method can be used. -->
      <username>#{sha512(username)}</username>
    </obfuscation>
  </buildScan>
</gradleEnterprise>
Obfuscating the hostnames using the Programmatic configuration
import java.util.stream.Collectors;
...
BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.obfuscation(obfuscation -> obfuscation.hostname(h -> h.chars().mapToObj(c -> String.valueOf(Character.getNumericValue(c))).collect(Collectors.joining())));
Obfuscating the hostnames cannot be configured via command-line argument.
Obfuscating the hostnames via gradle-enterprise.xml (Maven extension v1.6.3+)
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use a redacted value. -->
      <hostname>obfuscated</hostname>
    </obfuscation>
  </buildScan>
</gradleEnterprise>
---
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use Spring Expression Language. -->
      <hostname>#{isTrue(env['CI']) ? 'CI agent' : 'Local agent'}</hostname>
    </obfuscation>
  </buildScan>
</gradleEnterprise>
Obfuscating the IP addresses using the Programmatic configuration
import java.util.stream.Collectors;
...
BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.obfuscation(obfuscation -> obfuscation.ipAddresses(addresses -> addresses.stream().map(address -> "0.0.0.0").collect(Collectors.toList())));
Obfuscating the IP addresses cannot be configured via command-line argument.
Obfuscating the IP addresses via gradle-enterprise.xml (Maven extension v1.6.3+)
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use a redacted value.-->
      <ipAddresses>#{{'0.0.0.0'}}</ipAddresses>
    </obfuscation>
  </buildScan>
</gradleEnterprise>
---
<gradleEnterprise>
  <buildScan>
    <obfuscation>
      <!-- Use Spring Expression Language. The 'sha512' method can be used. The 'ipAddresses' variable can be used to get access to the list of IP addresses that would be captured. -->
      <ipAddresses>#{sha512(ipAddresses)}</ipAddresses>
    </obfuscation>
  </buildScan>
</gradleEnterprise>

See the BuildScanApi#obfuscation() API reference for more information.

Troubleshooting

Failed background build scan uploads

When using background build scan uploading (default behaviour since Maven extension version 1.5, see this section for configuration options) upload failures are not visible in the build logging due to occurring in a background process after the build has finished. Instead, errors are logged to a file located at ~/.m2/.gradle-enterprise/build-scan-data/upload-failure.log. If this additional information does not help to resolve the failure, please contact technical support and include the contents of this log file.

If the background upload process fails to start, a warning is shown in the build console and uploading is performed in the build process. If this occurs, please contact technical support with the log files located at ~/.m2/.gradle-enterprise/build-scan-data/<<extension-version>>/pending-uploads/*.log.

Slow resolution of host name

Build scans attempt to determine the host name of the machine. An issue affecting macOS can cause a delay when doing this in some environments.

If you see a warning during your build that resolving the local host name is slow, you can workaround the problem by adding a host name mapping 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

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.

Please refer to the build cache guide for step-by-step instructions on how to get started and in-depth explanations of important concepts. Moreover, the guide shows how to measure the effectiveness of the build cache in your project and explains how to roll out the build cache in your organization.

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.

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setServer("https://gradle-enterprise.mycompany.com");
$ mvn package -Dgradle.enterprise.url=https://gradle-enterprise.mycompany.com
Add the following to 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:

BuildScanApi buildScan = (BuildScanApi) mavenSession.lookup("com.gradle.maven.extension.api.scan.BuildScanApi");
buildScan.setAllowUntrusted(true);
Allowing untrusted certificate cannot be done via command-line argument.
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <server>
    <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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getLocal().setEnabled(false);
$ mvn package -Dgradle.cache.local.enabled=false
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <local>
      <enabled>false</enabled>
    </local>
  </buildCache>
</gradleEnterprise>
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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getLocal().setDirectory(new File("path/to/local/build-cache"));
$ mvn package -Dgradle.cache.local.directory=/path/to/local/build-cache
Add the following to 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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getLocal().getCleanupPolicy().setEnabled(false);
$ mvn package -Dgradle.cache.local.cleanup.enabled=false
Add the following to 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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getLocal().getCleanupPolicy().setRetentionPeriod(java.time.Period.ofDays(30));
buildCache.getLocal().getCleanupPolicy().setCleanupInterval(java.time.Period.ofDays(10));
$ mvn package -Dgradle.cache.local.cleanup.retention=P30D -Dgradle.cache.local.cleanup.interval=P10D
Add the following to 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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getRemote().setUrl(java.net.URI.create("http://my-node/cache/"));
$ mvn package -Dgradle.cache.remote.url=http://my-node/cache/
Add the following to 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:

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getRemote().setAllowUntrusted(true);
$ mvn package -Dgradle.cache.remote.allowUntrustedServer=true
Add the following to 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 either in the settings.xml file or in the gradle-enterprise.xml file.

When configuring the credentials 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>my-username</username>
    <password>my-password</password>
  </server>
</servers>

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

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getRemote().setServerId("my-node");
$ mvn package -Dgradle.cache.remote.serverId=my-node
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <server>
        <id>my-node</id>
        <url>http://my-node/cache/</url>
      </server>
    </remote>
  </buildCache>
</gradleEnterprise>

Alternatively, you can specify the credentials for the cache node explicitly.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getRemote().getCredentials().setUsername("my-username");
buildCache.getRemote().getCredentials().setPassword("my-password");
$ mvn package -Dgradle.cache.remote.username=my-username -Dgradle.cache.remote.password=my-password
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <server>
        <credentials>
          <username>my-username</username>
          <password>my-password</password>
        </credentials>
      </server>
    </remote>
  </buildCache>
</gradleEnterprise>

Credentials specified in your gradle-enterprise.xml file take precedence over the credentials specified in your settings.xml file. Instead of putting plain-text passwords into the configuration file, you should inject them via environment variables as demonstrated below.

gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <server>
        <credentials>
          <username>${env.GRADLE_ENTERPRISE_CACHE_USERNAME}</username>
          <password>${env.GRADLE_ENTERPRISE_CACHE_PASSWORD}</password>
        </credentials>
      </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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getRemote().setEnabled(false);
$ mvn package -Dgradle.cache.remote.enabled=false
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <enabled>false</enabled>
    </remote>
  </buildCache>
</gradleEnterprise>
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.

Using the Programmatic configuration (Maven extension v1.7+)
BuildCacheApi buildCache = (BuildCacheApi) mavenSession.lookup("com.gradle.maven.extension.api.cache.BuildCacheApi");
buildCache.getRemote().setStoreEnabled(true);
$ mvn package -Dgradle.cache.remote.storeEnabled=true
Add the following to gradle-enterprise.xml
<gradleEnterprise>
  <buildCache>
    <remote>
      <storeEnabled>true</storeEnabled>
    </remote>
  </buildCache>
</gradleEnterprise>

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.

Runtime classpath normalization

A common pattern is to have some build logic that writes volatile data to some files. For example your build might write a timestamp to a build.properties file. This helps to identify the source of a build artifact. However, this also causes problems with build caching: when Maven executes tests, the runtime classpath becomes part of the cache key for that test run. Any resource file that is processed by Maven is also added to that runtime classpath. In consequence, if a build generates a build timestamp, this causes the runtime classpath to change on every build invocation, resulting in cache misses. To circumvent this situation the Maven extension provides several configuration options to normalize the runtime classpath in order to deal with this volatility.

Ignoring arbitrary 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.

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

Files matching the patterns META-INF/maven/**/pom.xml and META-INF/maven/**/pom.properties are always ignored.

Ignoring specific entries in properties files

(Maven extension 1.8+)

Since properties files are a common file format to store data generated by the build, the normalization DSL provides special support for ignoring specific entries in properties files. Again, ANT-style patterns can be used to match relevant properties files. In the following example any files named build.properties in com/example or any subdirectory are matched and the value of the property build.timestamp is ignored:

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <normalization>
            <runtimeClassPath>
              <propertiesNormalizations>
                <propertiesNormalization>
                  <path>com/example/**/build.properties</path>
                  <ignoredProperties>
                    <ignore>build.timestamp</ignore>
                  </ignoredProperties>
                </propertiesNormalization>
              </propertiesNormalizations>
            </runtimeClassPath>
          </normalization>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

A common location to store properties files is the META-INF directory. Thus, the normalization DSL provides a shortcut for normalizing files matching META-INF/**/*.properties

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <normalization>
            <runtimeClassPath>
              <metaInf>
                <ignoredProperties>
                  <ignore>app.version</ignore>
                </ignoredProperties>
              </metaInf>
            </runtimeClassPath>
          </normalization>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

Normalizing contents of META-INF

(Maven extension 1.8+)

The <metaInf> configuration element offers even more convenience configuration settings for normalizing the contents of the META-INF directory. For example, you may only want to ignore one or more attributes in MANIFEST files, e.g. Implementation-Version instead of ignoring the whole file. This can be done as follows:

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <normalization>
            <runtimeClassPath>
              <metaInf>
                <ignoredAttributes>
                  <ignore>Implementation-Version</ignore>
                </ignoredAttributes>
              </metaInf>
            </runtimeClassPath>
          </normalization>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

If you want to ignore MANIFEST files completely there’s the <ignoreManifest> configuration as a shorthand for that. It’s the equivalent of adding META-INF/MANIFEST.MF as an ignored file.

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <normalization>
            <runtimeClassPath>
              <metaInf>
                <ignoreManifest>true</ignoreManifest>
              </metaInf>
            </runtimeClassPath>
          </normalization>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

In situations where you have several changing files in the META-INF directory, you might want to ignore the contents of that directory completely. The <ignoreCompletely> configuration is a shorthand for adding META-INF/** to ignored files.

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <normalization>
            <runtimeClassPath>
              <metaInf>
                <ignoreCompletely>true</ignoreCompletely>
              </metaInf>
            </runtimeClassPath>
          </normalization>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

The more you ignore using runtime classpath normalization, the more likely false cache hits become. You should use these features with care and try to narrow down the scope of normalizations as much as possible, e.g. you should prefer normalizing a single MANIFEST attribute over ignoring the META-INF directory completely.

Declaring inputs and outputs

The build cache works based on inputs and outputs. For each supported goal execution a cache key is calculated by inspecting all inputs. The cache key is then used to lookup the outputs of that execution in the cache. If no result can be found, the goal is executed and the outputs are stored in the cache under the cache key. This section explains how input and outputs can be fine-tuned.

Adding additional inputs and outputs to a plugin or execution

The predefined inputs and outputs of any supported goal can be augmented by declaring them in the <pluginManagement> section of your pom.xml file. A common use case for declaring additional input files are test cases that read from a location that is not automatically tracked. For example, you might have Cucumber specification files located in the src/test/specs directory. Any change to the files in that directory may change the result of running the tests. So changing specification files should result in rerunning the tests and not loading the results from the cache. Without additional configuration the build cache is unaware of these additional inputs and will therefore load the result from the cache even if specification files change.

Declaring additional inputs

The following will add the directory src/test/specs as an input to all executions of the Apache Maven Surefire plugin. Any files in that folder which match the given includes and excludes will then be tracked as part of the cache key:

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <plugins>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <inputs>
                <fileSets>
                  <fileSet>
                    <name>specs</name>
                    <paths>
                      <path>src/test/specs</path>
                    </paths>
                    <includes>
                      <include>**/*.feature</include>
                    </includes>
                    <excludes>
                      <exclude>archive/**/*.feature</exclude>
                    </excludes>
                    <normalization>RELATIVE_PATH</normalization>
                  </fileSet>
                </fileSets>
              </inputs>
            </plugin>
          </plugins>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

While processing inputs, various normalization strategies can be applied in order to raise the effectiveness of the build cache. In other words, normalization strategies are a way of ignoring changes to input files that are irrelevant for the goal execution. The following input normalization strategies are supported:

IGNORED_PATH

Considers the full content of files, but ignores their path.

NAME_ONLY

Considers the full content of files, but only tracks their name and not the rest of their path.

RELATIVE_PATH

The default strategy. Considers the full content of a file, but only tracks their path relative to their root directory. The root directory is the directory that was added as an input. The path of that root directory itself is ignored.

ABSOLUTE_PATH

Considers the full content of files as well as their absolute path. Using this strategy is strongly discouraged, as the project directory (and thus all absolute paths) are usually different on different machines, which prevents cache hits.

CLASSPATH

Considers only the information relevant for running Java code.

COMPILE_CLASSPATH

Considers only the information relevant for compiling Java code. This means for example that only class files are considered and private implementation details like method bodies are ignored.

Ignoring empty directories

(Maven extension 1.8+)

The RELATIVE_PATH and ABSOLUTE_PATH strategies additionally come in two flavors. Their sensitivity to the presence of empty directories in the source tree can be controlled. The default is to take empty directories into account. This means that adding an empty directory to the source tree will result in a cache miss. The following configuration causes empty directories to be ignored:

<normalization>
  <strategy>RELATIVE_PATH</strategy>
  <ignoreEmptyDirectories>true</ignoreEmptyDirectories>
</normalization>

A common reason for having empty directories in the source tree is using a version control system like Git that only keeps tracks of files. When one developer deletes a directory tree on their machine and another developer pulls that change from the repository, Git will only delete the files contained in that directory tree leaving parent directories empty.

Declaring additional outputs

Cucumber can be configured to generate various types of reports. These reports may be located in an output folder different from the default test report folder. In order to store them in the cache for later retrieval the output location needs to be declared as an additional output. The following will add the additional output directory ${project.build.directory}/cucumber to the default-test execution of the surefire plugin. The contents of this directory will become part of the cache archive for this execution.

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <plugins>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <executions>
                <execution>
                  <id>cucumber-tests</id>
                  <outputs>
                    <directories>
                      <directory>
                        <name>cucumber-reports</name>
                        <path>${project.build.directory}/cucumber</path>
                      </directory>
                    </directories>
                  </outputs>
                </execution>
              </executions>
            </plugin>
          </plugins>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

Configuring caching for a specific goal execution is not limited to declaring additional outputs. The same constructs for defining additional inputs that have been shown before can also be used for specific goal executions.

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:

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <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>
  </plugins>
</pluginManagement>

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 systems-integration-test execution of the failsafe plugin. Other tests will remain cacheable.

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

Making other goals cacheable

The extension allows you to make any goal cacheable, beyond the ones that are supported out of the box. Take great care to define all of the goal’s inputs and outputs before doing so, to avoid false cache hits and follow-up errors.

When making goals cacheable, you don’t need to repeat the values of all their inputs and outputs. You can simply provide the name of each property and the extension will look up the value in the goal’s configuration. The extension will make sure that you have handled all configuration parameters of the goal in this way. If some parameter is irrelevant for the purposes of caching, e.g. because it only affects console output, you can tell the extension to ignore it.

For input properties, the extension supports all primitives, Strings, Enums and Collections, Arrays and Maps of those. Any other types need to be broken down using the nestedProperties (for a single complex type) or iteratedProperties (for a Collection of complex types) configuration.

<pluginManagement>
  <plugins>
    <plugin>
      <groupId>com.gradle</groupId>
      <artifactId>gradle-enterprise-maven-extension</artifactId>
      <configuration>
        <gradleEnterprise>
          <plugins>
            <plugin>
              <groupId>my.company</groupId>
              <artifactId>awesome-but-slow-plugin</artifactId>
              <inputs>
                <fileSets>
                  <fileSet>
                    <name>sources</name>
                    <includesProperty>includes</includesProperty>
                    <excludesProperty>excludes</excludesProperty>
                  </fileSet>
                </fileSets>
                <properties>
                  <property>
                    <name>encoding</name>
                  </property>
                </properties>
                <ignoredProperties>
                  <ignore>logWarnings</ignore>
                </ignoredProperties>
              </inputs>
              <nestedProperties>
                <property>
                  <name>forkOptions</name>
                  <inputs>
                    <properties>
                      <property>
                        <name>maxHeap</name>
                      </property>
                    </properties>
                  </inputs>
                </property>
              </nestedProperties>
              <iteratedProperties>
                <property>
                  <name>targetPlatforms</name>
                  <inputs>
                    <properties>
                      <property>
                        <name>architecture</name>
                      </property>
                      <property>
                        <name>linkingMode</name>
                      </property>
                    </properties>
                  </inputs>
                </property>
              </iteratedProperties>
              <outputs>
                <directories>
                  <directory>
                    <name>outputDir</name>
                  </directory>
                </directories>
                <cacheableBecause>this plugin has CPU-bound goals with well-defined inputs and outputs</cacheableBecause>
              </outputs>
            </plugin>
          </plugins>
        </gradleEnterprise>
      </configuration>
    </plugin>
  </plugins>
</pluginManagement>

Troubleshooting

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 Maven build comparison feature can be used. Simply run the same Maven build twice and compare those two builds. To make it easier to find unstable input files capturing of goal input files should be explicitly enabled using -Dgradle.scan.captureGoalInputFiles=true

Capturing goal input files has an impact on build performance. For this reason it is disabled by default.

Once the build scans have been published they can be compared in Gradle Enterprise. In this example, the build was configured to write a timestamp to the build.properties file. When comparing the two builds this shows up nicely in the comparison.

comparison unstable input

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>

If you’re using the maven-jaxb2-plugin it’s still a good idea to remove unnecessary instability from its outputs. However, the Gradle Enterprise Maven Extension supports caching its generate goal even if file headers are being generated. Thus, downstream goals will not be affected by its changing outputs when they are loaded from the cache.

Some of the Maven JAXB plugins generate code based on the current system’s locale if not configured otherwise. This leads to unstable outputs depending on the configuration of the machine that executes the build, which in turn can lead to cache misses. For this reason the locale to use during code generation should be explicitly configured. Both the jaxb2-maven-plugin and the maven-jaxb2-plugin provide a <locale> option for this.

maven-bundle-plugin

By default the maven-bundle-plugin generates a timestamp in 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>
maven-failsafe-plugin

The maven-failsafe-plugin provides two goals: integration-test and verify. The former runs in the integration-test phase of the build and writes its results to a summary file. The latter runs in the verify phase, reads the summary file, and fails the build in case of test failures.

When configuring multiple Failsafe executions, they use the same output location for the summary file by default. This will prevent all but the first execution of the integration-test goal to be cacheable due to overlapping outputs. In order to get cache hits for all executions, you should configure a different summary file for each of them:

pom.xml
<plugin>
  <artifactId>maven-failsafe-plugin</artifactId>
  <executions>
    <execution>
      <id>first-execution</id>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <!-- ... -->
        <summaryFile>${project.build.directory}/failsafe-reports/first-failsafe-summary.xml</summaryFile>
      </configuration>
    </execution>
    <execution>
      <id>second-execution</id>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <!-- ... -->
        <summaryFile>${project.build.directory}/failsafe-reports/second-failsafe-summary.xml</summaryFile>
      </configuration>
    </execution>
  </executions>
</plugin>

If you have configured multiple executions that execute the same or an overlapping set of test classes (e.g. with different parameters), you should in addition change the reports directory, for example:

pom.xml
<plugin>
  <artifactId>maven-failsafe-plugin</artifactId>
  <executions>
    <execution>
      <id>first-execution</id>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <!-- ... -->
        <reportsDirectory>${project.build.directory}/first-failsafe-reports</reportsDirectory>
        <summaryFile>${project.build.directory}/first-failsafe-reports/failsafe-summary.xml</summaryFile>
      </configuration>
    </execution>
    <execution>
      <id>second-execution</id>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <!-- ... -->
        <reportsDirectory>${project.build.directory}/second-failsafe-reports</reportsDirectory>
        <summaryFile>${project.build.directory}/second-failsafe-reports/failsafe-summary.xml</summaryFile>
      </configuration>
    </execution>
  </executions>
</plugin>

Rerunning goals to deal with invalid cache entries

In rare circumstances the cache might be filled with an invalid entry, e.g. when another process deletes the outputs of a goal while the cache entry is being created. In this case you can use the -DrerunGoals command line argument to rerun the goals and overwrite the faulty cache entry.

Using test distribution

(Maven extension 1.8+, Gradle Enterprise 2020.5+)

Gradle Enterprise test distribution takes your existing test suites and distributes them across remote agents to execute them faster.

See this demo video for a quick introduction to the concept as well as an example of this breakthrough technology in action based on a real-world example.

How it works

test distribution network diagram

Historical test execution data provided by Gradle Enterprise is used to create balanced partitions of similar expected execution times in order to optimize distribution across remote agents. The tests and their supporting files are transferred to each agent and executed, with their logging and results streamed back to the build in real time. If there are no remote agents available, tests can still be executed locally on the build machine.

Gradle Enterprise provides an interface for administering remote agents, and viewing the current and historical system usage. Please see the Gradle Enterprise Admin Manual for more information.

Compatible types of tests

The extension enhances the execution of the test goal of the maven-surefire-plugin and the integration-test goal of the maven-failsafe-plugin and requires tests to run with Java 8 or later.

Tests must run via JUnit Platform, which is part of JUnit 5. All JUnit Platform compatible test engines are supported, such as JUnit Jupiter, jqwik, Kotest, Spek, and others.

JUnit 3 and JUnit 4 based tests are supported when using the JUnit Vintage Engine, including extensions such as Spock.

See Surefire’s documentation for guidance on how to configure it to use the JUnit Platform.

TestNG is currently not supported.

External test resources

Consideration must be given to any supporting resources required by tests.

Test distribution takes your tests and executes them on remote agents. The test runtime classpath and any additionally declared file inputs are transferred to the agents. If your tests are expecting any other resources to be locally available, such as a running database, the test agents must also provide the resource or the tests will fail.

There are two possible approaches to using such resources in your tests:

  1. Have the tests themselves provision necessary resources (e.g. using Docker containers)

  2. Deploy test distribution agents in environments that provide all required resources

The first approach is recommended where possible as it is more portable and minimizes the need to change agent environments when test resources change. The Testcontainers project provides an easy way to utilize Docker containers within tests.

Depending on how you deploy test distribution agents, additional configuration may be required in order to make Docker accessible to the tests running on those agents. Please refer to the Test Distribution Agent User Manual for more information on this topic.

When using the second approach of having the agent environment provide resources, it is important to declare the requirements of tests so that appropriate agents that provide the capability can be assigned.

Prerequisites

Test distribution for Maven requires a Gradle Enterprise 2020.5 or later installation with connected test distribution agents. For information on configuring a Gradle Enterprise installation to enable test distribution, please see the Gradle Enterprise Admin Manual.

Your build must use version 2.22 or later of the maven-surefire-plugin or maven-failsafe-plugin.

Java 16 or later

If you’re running Maven on Java 16 or later, you need to configure a few additional JVM arguments.

Add the following to .mvn/jvm.config
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.nio=ALL-UNNAMED

Alternatively, you can use the MAVEN_OPTS environment variable to configure these options.

Your build must also authenticate with Gradle Enterprise and the user be granted the “Test distribution” access control role. Unauthenticated usage of test distribution is prohibited due to the inherent security implications of such a remote code execution service.

The Gradle Enterprise Maven extension connects to the Gradle Enterprise server using a WebSocket connection. In case the extension cannot connect to the Gradle Enterprise server, please ensure all load balancers and proxies that are used between both ends support WebSocket connections.

Configuration

The examples in this section all reference the maven-surefire-plugin but the same configuration options are available for the maven-failsafe-plugin.

Enabling test distribution

Test distribution is enabled and configured per goal, via the distribution element in the POM.

pom.xml
<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M5</version>
  <configuration>
    <properties>
      <distribution>
        <enabled>true</enabled> (1)
      </distribution>
    </properties>
  </configuration>
</plugin>
1 Enable test distribution
Transferring test inputs & outputs between build and remote agents comes at a cost. For this reason you should not activate test distribution for each test goal in the build. Good candidates for distribution are test goals that take more than a few seconds to execute. Test goals that only execute fast unit tests should not be distributed as they will compete for remote agents thereby slowing other test distribution jobs down.

Executors

Tests can be executed by local and/or remote executors.

By default, the number of local executors is derived from the forkCount parameter of the goal. To limit the use of local executors, configure the maxLocalExecutors parameter. Setting it to zero, causes tests to be only executed remotely.

pom.xml
<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M5</version>
  <configuration>
    <properties>
      <distribution>
        <enabled>true</enabled>
        <maxLocalExecutors>2</maxLocalExecutors> (1)
      </distribution>
    </properties>
  </configuration>
</plugin>
1 Limit number of local executors to 2

By default, test execution uses as many remote executors as possible. To limit the number used, you can configure the maxRemoteExecutors parameter:

pom.xml
<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M5</version>
  <configuration>
    <properties>
      <distribution>
        <enabled>true</enabled>
        <maxRemoteExecutors>2</maxRemoteExecutors> (1)
      </distribution>
    </properties>
  </configuration>
</plugin>
1 Limit number of remote executors to 2

Each test goal will request at most maxRemoteExecutors (or as many as possible if not set) and test goals that are running in parallel will each request their respective configured number of remote executors. So a build running two test goals in parallel each having maxRemoteExecutors set to 10 will request 20 remote executors. The number of remote executors actually used is subject to agent availability and the number of test partitions.

Setting the number of remote executors to zero causes tests to be only executed locally.

When first enabling test distribution for an existing test suite, it is a good idea to start with only local executors to verify compatibility of the tests with test distribution.
When using Maven’s offline mode, only local executors will be used.

Requirements

Test goals can specify requirements that agents must fulfill in order to be considered compatible. Requirements are matched against the advertised capabilities of the agents that are connected to your Gradle Enterprise server. Only agents that fulfill all requirements are considered compatible. Local executors are assumed to provide all required capabilities. This means that local executors will always be used regardless of the declared requirements, unless use of local executors is disabled.

Requirements are strings that may only contain alphanumeric characters, dashes, underscores, periods, and a single equals sign. It is recommended to use a key-value form (e.g. os=linux) or tag-like form (e.g. postgres) to model requirements.

pom.xml
<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M5</version>
  <configuration>
    <properties>
      <distribution>
        <enabled>true</enabled>
        <requirements>
          <requirement>os=linux</requirement> (1)
          <requirement>postgres</requirement> (2)
        </requirements>
      </distribution>
    </properties>
  </configuration>
</plugin>
1 Add os=linux requirement
2 Add postgres requirement

To ensure tests are executed using the intended Java version, an implicit jdk=«version» requirement is added to the set of declared requirements for the goal. The version is determined from the Java executable set on the goal. Only the major version is taken into account (e.g. jdk=8 for Java 8 and jdk=11 for Java 11).

For more information on how to define the capabilities of an agent, please see the Test Distribution Agent User Manual.

Wait timeout

Test distribution relies on external services in order to execute your build, such as the Gradle Enterprise server and assigned remote agents. When an agent disconnects during the build, the extension reschedules the affected test partition on another local or remote executor. Similarly, when the connection to the Gradle Enterprise server is closed unexpectedly, the extension attempts to reconnect and reschedules the affected test partitions. However, in cases test execution cannot progress, e.g. if local executors are disabled and reconnection attempts are not successful, the extension will fail the build after a configurable wait timeout (30 seconds by default) has elapsed in order to prevent the build from blocking indefinitely.

pom.xml
<plugin>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M5</version>
  <configuration>
    <properties>
      <distribution>
        <enabled>true</enabled>
        <waitTimeoutInSeconds>120</waitTimeoutInSeconds> (1)
      </distribution>
    </properties>
  </configuration>
</plugin>
1 Configure wait timeout of 120 seconds
As long as local executors are configured the build can always progress and thus will not time out. If local executors are disabled but there are some compatible remote executors connected, the build will wait as long as it takes for them to become available even if that takes longer than waitTimeout.

Inputs and outputs

All well-known and manually declared inputs of the test goal are tracked by test distribution and will be transferred to remote executors. In order to add additional input files that are required for your tests declare them using in the POM configuration of the Gradle Enterprise Maven extension:

pom.xml
<build>
  <plugins>
    <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>3.0.0-M5</version>
      <configuration>
        <properties>
          <distribution>
            <enabled>true</enabled>
          </distribution>
        </properties>
      </configuration>
    </plugin>
  </plugins>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>com.gradle</groupId>
        <artifactId>gradle-enterprise-maven-extension</artifactId>
        <configuration>
          <gradleEnterprise>
            <plugins>
              <plugin>
                <artifactId>maven-surefire-plugin</artifactId> (1)
                <inputs>
                  <fileSets>
                    <fileSet>
                      <name>test-data</name>
                      <paths>
                        <path>src/test-data/data.xml</path> (2)
                      </paths>
                    </fileSet>
                  </fileSets>
                </inputs>
              </plugin>
            </plugins>
          </gradleEnterprise>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</build>
1 Reference the maven-surefire-plugin
2 Declare src/test-data/data.xml as an additional input file
See Tests reading files from src/test/resources for a common example of missing input declarations.

Tests may produce file outputs, such as reports. After finishing execution of a test partition all output files that are registered on the test goal are transferred back from remote executors to the build. Test reports and JaCoCo coverage data are automatically tracked.

pom.xml
<build>
  <plugins>
    <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>3.0.0-M5</version>
      <configuration>
        <properties>
          <distribution>
            <enabled>true</enabled>
          </distribution>
        </properties>
      </configuration>
    </plugin>
  </plugins>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>com.gradle</groupId>
        <artifactId>gradle-enterprise-maven-extension</artifactId>
        <configuration>
          <gradleEnterprise>
            <plugins>
              <plugin>
                <artifactId>maven-surefire-plugin</artifactId> (1)
                <outputs>
                  <directories>
                    <directory>
                      <name>additional-outputs</name>
                      <path>${project.build.directory}/additional-outputs</path> (2)
                    </directory>
                  </directories>
                </outputs>
              </plugin>
            </plugins>
          </gradleEnterprise>
        </configuration>
      </plugin>
    </plugins>
  </pluginManagement>
</build>
1 Reference the maven-surefire-plugin
2 Declare target/additional-outputs as an additional output directory
If two partitions produce the same output file, the test goal will fail. This means that you must create unique files per test class. Directory structures are merged as long as they don’t contain conflicting files.

Other configuration parameters

In addition to the above configuration parameters, the Gradle Enterprise Maven extension respects most of the goal’s configuration. The following parameters influence test partitioning and the type and number of used local and remote executors.

forkCount

Used as default number of local executors. Disabling forking by setting forkCount=0 is not supported.

Test distribution agents always only run a single fork. Changing the forkCount setting therefore has no impact on remote test execution. If you want to better utilize compute resources of the machines hosting agents you should start several agent instances on a single machine.

debugForkedProcess (maven.surefire.debug user property)

If enabled, tests are executed on a single local executor that you can attach a debugger to as usual.

reuseForks

If disabled, each test partition will be limited to a single test class.

argLine, systemProperties, systemPropertiesFile, systemPropertyVariables

Used locally and on agents when forking JVMs to execute tests. Paths of inputs and outputs in JVM arguments are automatically replaced to match the agent’s temporary workspace.

By default, Surefire/Failsafe lets the forked JVM determine the maximum heap size based on the machine’s physical memory by default. Since the outcome depends on the used JVM version and implementation, it’s usually a good idea to configure -Xmx explicitly via the argLine configuration parameter.

environmentVariables, excludedEnvironmentVariables

The environment variables of the Maven process (excluding those in excludedEnvironmentVariables) along with those explicitly configured via environmentVariables are passed to locally forked JVMs. For remote JVMs, only explicitly configured environment variables are propagated. Paths of inputs and outputs in environment variable values are automatically replaced to match the agent’s temporary workspace. In addition, remote JVMs inherit the environment variables of the agent process. You should prefer using system properties over environment variables to inject values into your tests, whenever possible.

rerunFailingTestsCount

If set to a value greater than 0, failing tests will be retried the specified number of times.

The following configuration parameters are currently not supported: enableProcessChecker, forkedProcessExitTimeoutInSeconds, forkNode, junitArtifactName, objectFactory, parallel, parallelOptimized, parallelTestsTimeoutForcedInSeconds, parallelTestsTimeoutInSeconds, perCoreThreadCount, runOrder, shutdown, suiteXmlFiles, testNGArtifactName, threadCount, threadCountClasses, threadCountMethods, threadCountSuites, useManifestOnlyJar, useSystemClassLoader, useUnlimitedThreads.

Integration with other plugins

JaCoCo plugin

Test distribution is compatible with JaCoCo Maven plugin. Coverage information from all executors, including remote, are merged to produce a single coverage report. No additional configuration is required.

Troubleshooting

Identifying the executor of a failed test

The build log contains information about the test executor used and the partition number for every test class. This information can help if the failure is caused by the test executor, such as a remote agent not providing a required resource.

$ mvn clean test
[…]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running TestClass1 (of partition 1 on localhost-executor-1)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.124 s - in TestClass1 (of partition 1 on localhost-executor-1)
[INFO] Running TestClass2 (of partition 2 on prj-distribution-agent-1)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.124 s - in TestClass2 (of partition 2 on prj-distribution-agent-1)
[INFO] Running TestClass3 (of partition 3 on prj-distribution-agent-2)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.124 s - in TestClass3 (of partition 3 on prj-distribution-agent-2)
[…]

Executors starting with localhost- are the local executors. All other executors (in our example containing distribution-agent) are the remote executors.

Moreover, the information is persisted in the generated XML reports. However, it is currently not visible in build scans.

Classes sharing static state

Each test partition executes in a separate JVM. If tests share state via static variables, intentionally or unintentionally, they may not work reliably when distributed. They are also unlikely to work reliably when not using test distribution if using forkCount to execute tests in parallel locally.

If your tests already set reuseForks = false to mitigate shared static state, they will likely work reliably with test distribution but be slower to execute than they otherwise would be. This setting causes a partition to be created per test class, which creates additional overhead when there are many test classes.

To make optimal use of test distribution, each test class should be independent of any other test class and not mutate any shared static state. If that is not possible, consider separating test classes that are self-contained from those that aren’t into separate test goals and configuring them accordingly.

Tests reading files from src/test/resources

Some tests access test resource files in their source location, e.g. in src/test/resources. This location is not an input to the test goal and files will not be available when tests are executed remotely. The maven-resources-plugin copies these files target/test-classes before executing tests. This location is declared as an input, and is part of the test runtime classpath.

One potential solution is to access such files from their location in the target folder instead. However, this does not work in IDEs that don’t delegate test execution to Maven as they use src/test/resources as an input. If you are unable to use Maven for all test execution, a better solution is to access the resource via the runtime classpath via Java’s classpath resource loading mechanism.

Provisioning an external resource for many tests

If you need to prepare/cleanup resources, start/stop a server etc. before/after your tests are executed, you can use one of JUnit Platform’s listener mechanisms. For that purpose, you need to add a dependency on junit-platform-launcher to your project:

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>test-execution-listener</artifactId>
  <version>1.0</version>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId> (1)
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId> (2)
      <scope>test</scope>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.junit</groupId>
        <artifactId>junit-bom</artifactId>
        <version>5.8.0-M1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <release>11</release>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M5</version>
        <configuration>
          <properties>
            <distribution>
              <enabled>true</enabled>
            </distribution>
          </properties>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
1 Use JUnit Platform for executing tests
2 Use JUnit Platform Launcher API for implementing a custom listener
JUnit 5.8 and later

With LauncherSessionListener, JUnit 5.8 introduced new hooks that are called before the first and after the last test in a forked test JVM, respectively, and are therefore well suited for implementing once-per-JVM setup/teardown behavior. A custom listener that starts an HTTP server before executing the first test and stops it after the last test has been executed, could look like this:

src/test/java/com/example/GlobalSetupTeardownListener.java
package com.example;

import com.sun.net.httpserver.HttpServer;
import org.junit.platform.launcher.LauncherSession;
import org.junit.platform.launcher.LauncherSessionListener;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class GlobalSetupTeardownListener implements LauncherSessionListener {

    private Fixture fixture;

    @Override
    public void launcherSessionOpened(LauncherSession session) {
        // Avoid setup for test discovery by delaying it until tests are about to be executed
        session.getLauncher().registerTestExecutionListeners(new TestExecutionListener() {
            @Override
            public void testPlanExecutionStarted(TestPlan testPlan) {
                if (fixture == null) {
                    fixture = new Fixture();
                    fixture.setUp();
                }
            }
        });
    }

    @Override
    public void launcherSessionClosed(LauncherSession session) {
        if (fixture != null) {
            fixture.tearDown();
            fixture = null;
        }
    }

    static class Fixture {

        private HttpServer server;
        private ExecutorService executorService;

        void setUp() {
            try {
                server = HttpServer.create(new InetSocketAddress(0), 0);
            } catch (IOException e) {
                throw new UncheckedIOException("Failed to start HTTP server", e);
            };
            server.createContext("/test", exchange -> {
                exchange.sendResponseHeaders(204, -1);
                exchange.close();
            });
            executorService = Executors.newCachedThreadPool();
            server.setExecutor(executorService);
            server.start(); (1)
            System.setProperty("http.server.port", String.valueOf(server.getAddress().getPort())); (2)
        }

        void tearDown() {
            server.stop(0); (3)
            executorService.shutdownNow();
        }
    }

}
1 Start the HTTP server
2 Export its dynamic port as a system property for consumption by tests
3 Stop the HTTP server

This sample uses the HTTP server implementation from the jdk.httpserver module that comes with the JDK but would work similarly with any other server or resource. In order for the listener to be picked up by JUnit Platform, you need to register it as a service by adding a resource file with the following name and contents to your test runtime classpath (e.g. by adding the file to src/test/resources):

src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener
com.example.GlobalSetupTeardownListener

You can now use the resource from your test:

src/test/java/com/example/HttpTests.java
package com.example;

import org.junit.jupiter.api.Test;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import static org.junit.jupiter.api.Assertions.assertEquals;

class HttpTests {

    @Test
    void respondsWith204() throws Exception {
        var httpClient = HttpClient.newHttpClient();
        var port = System.getProperty("http.server.port"); (1)
        var request = HttpRequest.newBuilder(URI.create("http://localhost:" + port + "/test")).build();

        var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); (2)

        assertEquals(204, response.statusCode()); (3)
    }
}
1 Read the port of the server from the system property set by the listener
2 Send a request to the server
3 Check the status code of the response
JUnit 5.7 and before

If you’re using a JUnit version prior to 5.8, you can implement a TestExecutionListener to achieve custom setup/teardown behavior.

Implementations of TestExecutionListener are called for every execution request, i.e. potentially multiple times per forked test JVM in the case of retries and when a forked JVM is reused. If possible, you should therefore upgrade to JUnit 5.8 or later and implement a LauncherSessionListener instead.

A custom listener that starts an HTTP server before executing the first test and stops it after the last test has been executed, could look like this:

src/test/java/com/example/CustomTestExecutionListener.java
package com.example;

import com.sun.net.httpserver.HttpServer;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CustomTestExecutionListener implements TestExecutionListener {

    private HttpServer server;
    private ExecutorService executorService;

    @Override
    public void testPlanExecutionStarted(TestPlan testPlan) {
        try {
            server = HttpServer.create(new InetSocketAddress(0), 0);
        } catch (IOException e) {
            throw new UncheckedIOException("Failed to start HTTP server", e);
        };
        server.createContext("/test", exchange -> {
            exchange.sendResponseHeaders(204, -1);
            exchange.close();
        });
        executorService = Executors.newCachedThreadPool();
        server.setExecutor(executorService);
        server.start(); (1)
        System.setProperty("http.server.port", String.valueOf(server.getAddress().getPort())); (2)
    }

    @Override
    public void testPlanExecutionFinished(TestPlan testPlan) {
        server.stop(0); (3)
        executorService.shutdownNow();
    }
}
1 Start the HTTP server
2 Export its dynamic port as a system property for consumption by tests
3 Stop the HTTP server

This sample uses the HTTP server implementation from the jdk.httpserver module that comes with the JDK but would work similarly with any other server or resource. In order for the listener to be picked up by JUnit Platform, you need to register it as a service by adding a resource file with the following name and contents to your test runtime classpath (e.g. by adding the file to src/test/resources):

src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener
com.example.CustomTestExecutionListener

You can now use the resource from your test:

src/test/java/com/example/HttpTests.java
package com.example;

import org.junit.jupiter.api.Test;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import static org.junit.jupiter.api.Assertions.assertEquals;

class HttpTests {

    @Test
    void respondsWith204() throws Exception {
        var httpClient = HttpClient.newHttpClient();
        var port = System.getProperty("http.server.port"); (1)
        var request = HttpRequest.newBuilder(URI.create("http://localhost:" + port + "/test")).build();

        var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); (2)

        assertEquals(204, response.statusCode()); (3)
    }
}
1 Read the port of the server from the system property set by the listener
2 Send a request to the server
3 Check the status code of the response

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.

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.

  • <classpath>/gradle-enterprise.xml is used for organization-wide or team-wide configuration and overrides the global configuration. This allows to package a gradle-enterprise.xml file in the root of a custom extension jar that can be reused across projects.

  • <project-dir>/.mvn/gradle-enterprise.xml is used for project-specific configuration and overrides the classpath 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. The latest version of the schema is available at https://www.gradle.com/schema/gradle-enterprise-maven.xsd, or you can get a specific schema version by appending the Gradle Enterprise Maven extension version to the schema location, e.g. https://www.gradle.com/schema/gradle-enterprise-maven-1.10.2.xsd. IntelliJ IDEA will mark unknown schemas as missing and they have to be explicitly fetched via the quick fix dialog (Alt + Enter). There is an open issue to make this more user friendly.

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>
    <!-- Whether to upload the build scan in background. Defaults to true. System property is 'gradle.scan.uploadInBackground' -->
    <backgroundBuildScanUpload>false</backgroundBuildScanUpload>
    <!-- Whether to capture content hashes of each input file for build scan comparison. Defaults to false. System property is 'gradle.scan.captureGoalInputFiles' -->
    <captureGoalInputFiles>true</captureGoalInputFiles>
    <!-- Obfuscated values for captured build scan data (optional). -->
    <obfuscation>
      <!-- The obfuscated username to capture (optional). -->
      <username></username>
      <!-- The obfuscated hostname to capture for local and public hostnames (optional). -->
      <hostname></hostname>
      <!-- The obfuscated IP addresses to capture (optional). -->
      <ipAddresses></ipAddresses>
    </obfuscation>
    <!-- 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>
        <!-- Optionally specify the credentials. The credentials specified here take precedence over the credentials in your settings.xml -->
        <credentials>
          <!-- The username to use to connect to an authenticated cache node. System property is 'gradle.cache.remote.username'. -->
          <username>some-username</username>
          <!-- The password to use to connect to an authenticated cache node. System property is 'gradle.cache.remote.password'. -->
          <password>some-password</password>
        </credentials>
        <!-- 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>

Expression support

The gradle-enterprise.xml configuration file supports two types of expressions that are evaluated when reading the configuration file:

${…​}

Maven-style expression (behaves like in pom.xml) for simple use cases, e.g. referencing an environment variable (e.g. ${env.CACHE_USERNAME})

#{…​}

Spring Expression Language (SpEL) expressions for more complex use cases, e.g. converting an environment variable into a boolean (e.g. #{env['CI'] == null})

For both expression types, the following objects can be referenced:

session

the current MavenSession object (e.g. ${session.request.cacheNotFound} or #{session.request.cacheNotFound})

basedir

the base directory of the build (e.g. ${basedir/src} or #{basedir}/src)

In addition, the following objects and functions can be referenced in SpEL expressions:

properties: Properties

user/system/profile properties (e.g. #{properties['user.dir']}). These are the same properties you can reference using the ${«property-name»} syntax.
User properties are passed via -D on the command-line. System properties are provided by the runtime and contain System.getProperties() as well as env.-prefixed environment variables. Profile properties refer to properties defined in active Maven profiles.

env: Map<String, String>

environment variables (e.g. #{env['CI']})

username: String

The username of the agent running the build (e.g. #{username})

ipAddresses: List<String>

The IP addresses of the agent running the build (e.g. #{ipAddresses})

isTrue(Object): boolean

returns true unless the supplied object’s String value case-insensitively equals false, 0, 0.0, or null (e.g. #{isTrue(true)})

isFalse(Object): boolean

returns true if the supplied object’s String value case-insensitively equals false, 0, 0.0, or null (e.g. #{isFalse(false)})

sha512(Object): Object

returns a SHA-512 String representation of the supplied object’s String value. If the supplied object is a List, returns a list of the individual sha512 call for each item of the list.

The following example shows how to use both expression types to configure local and CI builds with a single gradle-enterprise.xml file. It uses the JENKINS_URL environment variable (which is present in builds on Jenkins) to determine whether the build is running locally or on CI. Based on that, it enables the local build cache and background build scan upload only for local builds but enables writing to the remote build cache only for CI builds. Moreover, it determines the URL of the remote build cache based on the fictional REGION environment variable. Lastly, it uses Maven-style expressions to configure the remote build cache credentials based on custom environment variables that are typically injected by the CI server.

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">
  <buildScan>
    <backgroundBuildScanUpload>#{env['JENKINS_URL'] == null}</backgroundBuildScanUpload>
    <obfuscation>
      <username>#{sha512(username)}</username>
      <hostname>#{isTrue(env['CI']) ? 'CI agent' : 'Local agent'}</hostname>
      <ipAddresses>#{sha512(ipAddresses)}</ipAddresses>
    </obfuscation>
  </buildScan>
  <buildCache>
    <local>
      <enabled>#{env['JENKINS_URL'] == null}</enabled>
    </local>
    <remote>
      <server>
        <url>#{env['REGION'].startsWith('us') ? 'https://us.example.org/cache' : 'https://eu.example.org/cache'}</url>
        <credentials>
          <username>${env.GRADLE_ENTERPRISE_CACHE_USERNAME}</username>
          <password>${env.GRADLE_ENTERPRISE_CACHE_PASSWORD}</password>
        </credentials>
      </server>
      <enabled>true</enabled>
      <storeEnabled>#{env['JENKINS_URL'] != null}</storeEnabled>
    </remote>
  </buildCache>
</gradleEnterprise>

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. The latest version of the schema is always available at https://www.gradle.com/schema/gradle-enterprise-maven-project.xsd, or you can get a specific schema version by appending the Gradle Enterprise Maven extension version to the schema location, e.g. https://www.gradle.com/schema/gradle-enterprise-maven-project-1.10.2.xsd. IntelliJ IDEA will mark unknown schemas as missing and they have to be explicitly fetched via the quick fix dialog (Alt + Enter). There is an open issue to make this more user friendly. Please note that auto-completion is currently only supported by Eclipse. For IntelliJ IDEA, there’s an open issue to add such a feature.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         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">

  <!-- other build configuration -->
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>com.gradle</groupId>
          <artifactId>gradle-enterprise-maven-extension</artifactId>
          <configuration>
            <gradleEnterprise xmlns="https://www.gradle.com/gradle-enterprise-maven-project">
              <normalization>
                <runtimeClassPath>
                  <ignoredFiles>
                    <ignoredFile>META-INF/build.properties</ignoredFile>
                  </ignoredFiles>
                  <propertiesNormalizations>
                    <propertiesNormalization>
                      <path>com/example/**/build.properties</path>
                      <ignoredProperties>
                        <ignore>build.timestamp</ignore>
                      </ignoredProperties>
                    </propertiesNormalization>
                  </propertiesNormalizations>
                  <metaInf>
                    <ignoreManifest>true</ignoreManifest>
                    <ignoreCompletely>true</ignoreCompletely>
                    <ignoredAttributes>
                      <ignore>Implementation-Version</ignore>
                    </ignoredAttributes>
                    <ignoredProperties>
                      <ignore>app.version</ignore>
                    </ignoredProperties>
                  </metaInf>
                </runtimeClassPath>
              </normalization>
              <plugins>
                <!-- an example of adding more details to an already cacheable plugin -->
                <plugin>
                  <artifactId>maven-failsafe-plugin</artifactId>
                  <inputs>
                    <fileSets>
                      <fileSet>
                        <name>samples</name>
                        <paths>
                          <path>src/test/samples</path>
                        </paths>
                        <includes>
                          <include>**/*.sample</include>
                        </includes>
                        <excludes>
                          <exclude>archive/**/*.sample</exclude>
                        </excludes>
                        <normalization>NAME_ONLY</normalization>
                      </fileSet>
                      <fileSet>
                        <name>config</name>
                        <paths>
                          <path>src/main/config</path>
                        </paths>
                        <normalization>
                          <strategy>RELATIVE_PATH</strategy>
                          <ignoreEmptyDirectories>true</ignoreEmptyDirectories>
                        </normalization>
                      </fileSet>
                    </fileSets>
                  </inputs>
                  <outputs>
                    <files>
                      <file>
                        <name>summary</name>
                        <path>target/test-results/summary.txt</path>
                      </file>
                    </files>
                    <directories>
                      <directory>
                        <name>screenshots</name>
                        <path>target/test-results/screenshots</path>
                      </directory>
                    </directories>
                    <notCacheableBecause>these tests verify integration with other systems and should rerun even if our
                      inputs didn't change
                    </notCacheableBecause>
                  </outputs>
                  <localState>
                    <fileSets>
                      <fileSet>
                        <name>someTemporaryStuff</name>
                        <paths>
                          <path>target/myTestFramework/tmp</path>
                        </paths>
                      </fileSet>
                    </fileSets>
                  </localState>
                </plugin>
                <plugin>
                  <artifactId>maven-compiler-plugin</artifactId>
                  <executions>
                    <execution>
                      <id>default-compile</id>
                      <inputs>
                        <!-- same as above -->
                      </inputs>
                      <outputs>
                        <!-- same as above -->
                      </outputs>
                      <localState>
                        <!-- same as above -->
                      </localState>
                    </execution>
                  </executions>
                </plugin>
                <!-- an example of making a custom plugin cacheable -->
                <plugin>
                  <groupId>my.company</groupId>
                  <artifactId>awesome-but-slow-plugin</artifactId>
                  <inputs>
                    <fileSets>
                      <fileSet>
                        <name>sources</name>
                        <includesProperty>includes</includesProperty>
                        <excludesProperty>excludes</excludesProperty>
                      </fileSet>
                    </fileSets>
                    <properties>
                      <property>
                        <name>encoding</name>
                      </property>
                    </properties>
                    <ignoredProperties>
                      <ignore>logWarnings</ignore>
                    </ignoredProperties>
                  </inputs>
                  <nestedProperties>
                    <property>
                      <name>forkOptions</name>
                      <inputs>
                        <properties>
                          <property>
                            <name>maxHeap</name>
                          </property>
                        </properties>
                      </inputs>
                    </property>
                  </nestedProperties>
                  <iteratedProperties>
                    <property>
                      <name>targetPlatforms</name>
                      <inputs>
                        <properties>
                          <property>
                            <name>architecture</name>
                          </property>
                          <property>
                            <name>linkingMode</name>
                          </property>
                        </properties>
                      </inputs>
                    </property>
                  </iteratedProperties>
                  <outputs>
                    <directories>
                      <directory>
                        <name>outputDir</name>
                      </directory>
                    </directories>
                    <cacheableBecause>this plugin has CPU-bound goals with well-defined inputs and outputs</cacheableBecause>
                  </outputs>
                  <localState>
                    <fileSets>
                      <fileSet>
                        <name>tempDir</name>
                      </fileSet>
                    </fileSets>
                  </localState>
                </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>
  </build>
</project>

Programmatic configuration

In order to access the Gradle Enterprise Maven extension API to perform programmatic configuration of build scans and the build cache you need to create a Maven extension. The Common Custom User Data Maven Extension provided by Gradle Inc. provides an example. This extension can be applied directly to your project, or can serve as a template project for your own extension implementation.

Please see the API reference for more details.

Appendix B: 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

    • Username (system property 'user.name') (Can be obfuscated)

    • Local hostname (environment variable 'COMPUTERNAME' / 'HOSTNAME') (Can be obfuscated)

    • Public hostname (Can be obfuscated)

    • Local IP addresses (Can be obfuscated)

    • Build Java Virtual Machine

    • Operating System

    • Hardware

  • Build

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

    • Build failure exception messages and stacktraces

    • Build console output

    • Projects

    • Executed goals

    • Executed tests (using Apache Maven Surefire plugin)

    • Applied plugins

    • Resolved dependencies

    • Network downloads (performed by Maven)

    • Build cache configuration

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 C: 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

Unless there are annotation processors on the classpath, the extension uses compile avoidance so your sources are only recompiled if the signatures of the classes on the compile classpath have changed.

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:

Test results for surefire:test are stored in the cache whenever the goal succeeds. Thus, by default, only successful or skipped test results are cached. However, if <testFailureIgnore> is set to true, test failures are cached as well.

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

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.

Caching is automatically disabled if:

To allow tests to be cached while using JaCoCo, have each surefire/failsafe execution write to a separate data file and use a jacoco:merge or jacoco:report-aggregate goal to create a merged data file or an aggregated report.

maven-jaxb2-plugin (org.jvnet.jaxb2.maven2)

Supported plugin artifact ids:

  • maven-jaxb2-plugin

  • maven-jaxb20-plugin

  • maven-jaxb21-plugin

  • maven-jaxb22-plugin

  • maven-jaxb23-plugin

Supported versions: 0.12.3 and above

Supported goals:

  • generate

Caching is automatically disabled if:

  • a non-local URL is used to declare a catalog, schema, or binding

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

  • non-local URL references to schemas or bindings with changing content from within schema or binding files

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

  • logging settings (verbose)

  • proxy settings (proxyHost, proxyPort, proxyUsername, proxyPassword, useActiveProxyAsHttpproxy)

  • settings for the plugin’s up-to-date check and incremental build feature (forceRegenerate, removeOldOutput, produces, cleanPackageDirectories)

maven-javadoc-plugin

Supported versions: 2.7 and above

Supported goals:

  • javadoc:javadoc

  • javadoc:javadoc-no-fork

  • javadoc:test-javadoc

  • javadoc:test-javadoc-no-fork

  • javadoc:jar

  • javadoc:test-jar

Caching is automatically disabled if:

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

maven-checkstyle-plugin

Supported versions: 2.14 and above

Supported goals:

  • check

  • checkstyle

  • checkstyle-aggregate

Caching is automatically disabled if:

The following properties are deliberately not tracked as inputs, because they do not influence the outcome of the goal:

Absolute paths in output files are ignored

Checkstyle’s output files contain absolute paths that are deliberately ignored by the extension. Thus, when loading the goal’s outputs from cache, the referenced paths might not exist on the machine that is executing the build. In case that’s problematic for you, please disable the cache for this goal.

Appendix D: Anatomy of the .gradle-enterprise directory

By default, the Gradle Enterprise Maven extension stores temporary data in the ${user.home}/.m2/.gradle-enterprise directory. If you want to customize this location, use one of the following options:

Using the Programmatic configuration (Maven extension 1.10.1+)
import java.nio.file.Paths
GradleEnterpriseApi gradleEnterprise = (GradleEnterpriseApi) mavenSession.lookup("com.gradle.maven.extension.api.GradleEnterpriseApi");
gradleEnterprise.setStorageDirectory(Paths.get("/path/to/new/storage/directory"));
$ mvn clean verify -Dgradle.enterprise.storage.directory=/path/to/new/storage/directory
Customizing the Gradle Enterprise Maven extension storage directory location is not available via XML configuration.

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

fingerprint-cache

Location of the local fingerprint cache

token-cache

Location of cached entitlement tokens

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

Appendix E: Release history

1.10.2

11th June 2021
  • Build scans: Snapshot dependency artifact resolutions of maven-metadata.xml artifacts are properly captured

  • Build scans: Out of bound test output events are not captured

  • Build scans: Also use programmatically configured storage directory (introduced in 1.10.1) for temporary files

Compatible with scans.gradle.com and Gradle Enterprise 2021.2 or later.

1.10.1

8th June 2021
  • Build scans: Introduce new GradleEnterpriseApi

  • Build scans: Restore support for custom locales (e.g. tr-TR)

  • Test distribution: JUnit Platform artifacts on the test runtime classpath/module path take precedence over those included in the extension

  • Test distribution: Merge JaCoCo coverage data when using a single remote executor along with local executors

Compatible with scans.gradle.com and Gradle Enterprise 2021.2 or later.

1.10

1st June 2021
  • Build scans: Capture test output

  • Build scans: Read user input without requiring the Plexus Interactivity dependency

  • Test distribution: Support Java Platform Module System (JPMS)

  • Test distribution: Time out and retry when upgrading a connection to WebSockets hangs

  • Test distribution: Exclude environment variables configured in the excludedEnvironmentVariables Surefire/Failsafe parameter when forking local test JVMs

  • Test distribution: Agents are released on agent shutdown once the current partition is finished

Compatible with scans.gradle.com and Gradle Enterprise 2021.2 or later.

1.9.2

7th May 2021
  • Build cache: Add support for xrefTestLocation property in maven-checkstyle-plugin 3.2.0

  • Build cache: Add support for dependencyLinks and addStylesheets properties in maven-javadoc-plugin 3.3.0

  • Test distribution: Fix cross-OS path mapping to cope with a broader range of inputs

  • Test distribution: Reuse temporary workspace on test distribution agents across sessions when reuseForks is false

  • Test distribution/build cache: Add support for includeJUnit5Engines and excludeJUnit5Engines property in maven-surefire-plugin and maven-failsafe-plugin 3.0.0-M6

Compatible with scans.gradle.com and Gradle Enterprise 2021.1 or later.

1.9.1

7th April 2021
  • Build scans: Reduced memory usage when capturing goal input files

  • Build scans: Fixed rare case of duplicated dependencies capturing

  • Build scans: Fixed captured exception messages in some cases

  • Test distribution: Fixed potentially hanging build when agents disconnect before starting test execution

  • Test distribution: Compatibility with JDK 16 without having to configure <argLine>

  • Test distribution: Improved reporting of unrecoverable failures that occurred on test distribution agents

  • Test distribution: Fixed interoperability issue when using test distribution agents running on Linux from builds running on Windows

  • Test distribution: Fixed reporting of output emitted during startup of forked JVMs on test distribution agents

  • Test distribution/build scans: Fixed reporting of tests that were in progress when a forked JVM crashed to be listed as "skipped"

Compatible with scans.gradle.com and Gradle Enterprise 2021.1 or later.

1.9

15th March 2021
  • Build scans: add Maven dependency resolution capturing

  • Build cache: stricter validation of the Gradle Enterprise pom.xml configuration

  • Build scans/test distribution: the GRADLE_ENTERPRISE_ACCESS_KEY environment variable allows specifying the access keys for multiple hosts

  • Test distribution: improve test scheduling by creating smaller partitions

  • Test distribution: add support for once-per-JVM setup/teardown behavior via implementations of LauncherSessionListener introduced in JUnit 5.8.0-M1

  • Test distribution: optimize local execution of single test classes

  • Test distribution: configuring maxLocalExecutors takes precedence over forkCount

  • Reduced memory usage for builds with lots of input files

Compatible with scans.gradle.com and Gradle Enterprise 2021.1 or later.

1.8.3

25th February 2021
  • Fix resource leakage when used from IntelliJ IDEA

  • Properly disambiguate parameterized TestNG tests

  • Add support for failOnFlakeCount property in maven-surefire-plugin 3.0.0-M6

Compatible with scans.gradle.com and Gradle Enterprise 2020.5 or later.

1.8.2

4th February 2021
  • Missing source roots are treated like empty source roots in build scan comparisons

  • Warnings are logged for unsupported Surefire/Failsafe config parameters even when explicitly set to their default value when Test Distribution is enabled

  • Test Distribution output file archives are sanity checked before unpacking to ensure only regular files and directories are created inside the target directory (CVE-2021-26719)

  • Temporary Test Distribution output files are stored in target/test-distribution-outputs instead of directly in test-distribution-outputs in the project’s base directory

Compatible with scans.gradle.com and Gradle Enterprise 2020.5 or later.

1.8.1

7th January 2021
  • Test goals without test classes no longer fail if test distribution is enabled without JUnit Platform test engine on the classpath

  • Potential deadlock when test distribution broker connection is lost while tests are being executed is now avoided

  • Output of JVM which discovers / executes tests is printed in case it exits with non-zero exit code

  • Failed attempts to read from or write to the fingerprint cache are now logged as errors but no longer cause the build to fail

  • Fix sporadic ArithmeticException when reconnecting to the Gradle Enterprise server for test distribution

  • File uploads with non-retryable failures now cause test goals to fail instead of making the build hang

  • Fix file path normalization to properly capture file roots (e.g. workspace, local Maven repository)

Compatible with scans.gradle.com and Gradle Enterprise 2020.5 or later.

1.8

8th December 2020
  • Added support for test distribution

  • Build cache now supports normalization of MANIFEST and properties files

  • Build cache now ignores empty directories being added or removed to the source tree when calculating cache keys for the maven-compiler-plugin and maven-checkstyle-plugin

  • The <normalization> element in the POM DSL now provides a way to specify that empty directories should be ignored during build cache key calculation

  • Tests with failed assumptions in @BeforeAll methods are now captured in build scans correctly

Compatible with scans.gradle.com and Gradle Enterprise 2020.5 or later.

1.7.2

20th November 2020
  • Track versions of JDK 15 and later correctly as input for code compilation, test execution, and Javadoc generation

  • Fix performance issue caused by querying the machine’s network interfaces for every goal execution

Compatible with scans.gradle.com and Gradle Enterprise 2020.4 or later.

1.7.1

13th November 2020
  • Fix race condition when using forked test VMs

Compatible with scans.gradle.com and Gradle Enterprise 2020.4 or later.

1.7

27th October 2020
  • Capture workspace directory project name as top level project name

  • Prevent build exception if using a Maven Surefire/Failsafe plugin < 2.12

  • Properly detect identical tests run by different forked VMs

  • Improve handling of invalid plugin cache configuration in Maven POM DSL

  • Set the content length header when provisioning access keys for improved compatibility with some load balancers

Compatible with scans.gradle.com and Gradle Enterprise 2020.4 or later.

1.6.8

6th October 2020
  • Correctly handle multiple occurrences of failed dependencies

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.7

22nd September 2020
  • Add support for runOrderRandomSeed property in maven-surefire-plugin and maven-failsafe-plugin 3.0.0-M6

  • Correctly handle multiple executions of the same Maven project

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.6

18th September 2020
  • Properly link logs from sub-processes to the parent goal execution

  • Protect against unexpected failures during project execution

  • Fix error with Maven build cache when executing a goal early

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.5

9th September 2020
  • Introduce BuildCacheApi to programmatically configure the Maven build cache

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.4

28th August 2020
  • Fix build scans publishing from IntelliJ 2020.2+

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.3

26th August 2020
  • Protect against potentially duplicated Maven SessionStarted execution event callback firing

  • Support obfuscation of identifying data via gradle-enterprise.xml

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.2

11th August 2020
  • Fix test capturing when the JUnit Platform is called from within test code with a modified class loader

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6.1

3rd August 2020
  • Fix project structure capturing for sub modules

  • Fix capturing of skipped test classes

  • Fix test capturing when the JUnit Platform is called from within test code

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.6

27th July 2020
  • Capture if the build scan was uploaded in the background

  • Capture the full project structure when the build is run from a sub-module

  • Do not print test capturing warning for projects with empty test classes directory

  • Fix potential socket stream security vulnerability when capturing tests (CVE-2020-15777)

Compatible with scans.gradle.com and Gradle Enterprise 2020.3 or later.

1.5.3

22nd June 2020
  • Build caching now supports custom javac executables configured for the maven-compiler-plugin

Compatible with scans.gradle.com and Gradle Enterprise 2020.2 or later.

1.5.2

5th June 2020
  • Maven Extension XML project XSD is fixed

  • Error when using null values in proxy configuration is fixed

Compatible with scans.gradle.com and Gradle Enterprise 2020.2 or later.

1.5.1

19th May 2020
  • Credentials for remote build cache can be specified via gradle-enterprise.xml

  • Support usage of SpEL expressions in gradle-enterprise.xml

  • Support supply of gradle-enterprise.xml via custom extension jar on the Maven classpath

  • Race condition in Apache Commons Logging class instantiation is mitigated

  • Stale build scan files are cleaned up

Compatible with scans.gradle.com and Gradle Enterprise 2020.2 or later.

1.5

5th May 2020
  • Uploads build scans in the background after the build has finished

  • Add caching support for Surefire forkNode and jdkToolchain properties

Compatible with scans.gradle.com and Gradle Enterprise 2020.2 or later.

1.4.1

1st April 2020
  • Fix capturing of unresolvable conflicts in Maven dependency graphs

Compatible with scans.gradle.com and Gradle Enterprise 2020.1 or later.

1.4

24th March 2020
  • Capture Maven dependencies in build scans

  • Fix test capturing when failure in class-level method happens under special conditions

Compatible with scans.gradle.com and Gradle Enterprise 2020.1 or later.

1.3.6

30th January 2020
  • Cache configuration declared using the POM DSL takes precedence over built-in configuration

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.3.5

21st January 2020
  • Extension is deactivated automatically in incompatible IntelliJ IDEA versions

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.3.4

16th January 2020
  • Add caching support for maven-javadoc-plugin 3.2.0

  • Add caching support for maven-checkstyle-plugin 3.1.1

  • Fix handling of compile source roots when loading outputs of the maven-compiler-plugin from cache

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.3.3

16th December 2019
  • Build cache does no longer store broken symbolic links

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.3.2

13th December 2019
  • Test capturing is disabled when tests are executed with Java < 8

  • Improved help message when authentication is required for build scan publishing

  • Mitigation if slow local host name resolution on macOS

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.3.1

9th December 2019
  • Users can provide obfuscation functions for captured username, local IP addresses and hostnames

  • String interpolation in Surefire argLine property is supported

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.3

25th November 2019
  • Add support for authenticated build scans feature

  • Add support for maven-javadoc-plugin 3.2.0

  • Add support for maven-surefire-plugin and maven-failsafe-plugin 3.0.0-M4

  • Logs on debug level are not captured

  • Ignore additional Maven extension applications, when another instance is already applied

Compatible with scans.gradle.com and Gradle Enterprise 2019.4 or later.

1.2.8

16th October 2019
  • Global gradle-enterprise.xml is now read from ${maven.home}/conf in Maven < 3.5.0

  • Fixed hanging build due to race condition when an error during data capturing occurred

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.7

1st October 2019
  • Extension behaves more lenient towards unusual embeddings of Maven

  • Goal cache key only contains the major Java version

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.6

18th September 2019
  • Reduced runtime and memory allocation overhead of test capturing

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.5

16th September 2019
  • Fixed project structure capturing when a goal is executed very early in the build

  • Fixed test capturing when a custom test provider is specified

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.4

10th September 2019
  • Fixed test capturing when test started event cannot be found

  • Fixed test capturing when duplicate JUnit 4 Descriptions are found

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.3

28th August 2019
  • Add executeOnce Maven build scan API

  • Fixed concurrency issue in test capturing

  • Handle multi-threading and fail-fast scenarios for Maven < 3.6.2

  • Handling of Maven workspace ID is enhanced

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.2

23rd August 2019
  • TestNG tests handle orphaned failure events

  • Fixed build hangs when failure occurred during test processing

  • Fixed exceptions in test capturing

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2.1

20th August 2019
  • Support failing @Before*/@After* annotations in all supported test frameworks

  • Support failing TestNG dependOnMethods and dependsOnGroups tests

  • Don’t fail when null test method name or test class name is encountered

  • Gradle Enterprise server set by configuration is now retrievable from the BuildScanApi

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.2

8th August 2019
  • Capture tests executed in Surefire/Failsafe 2.15+ for JUnit 4/5 and TestNG frameworks

  • Capture console output

  • Add adjacent build scans support for Maven by capturing the workspace ID, unique per project workspace

  • Fixed origin build scan link when there is no reference to the output producing goal

  • Fixed project structure capturing when other extensions/plugins update the internal Maven projects

  • Fixed event serialization error when using custom tags/links/values via the BuildScanApi

  • Enhanced the BuildScanApi:

    • Register a background action

    • Register an action to be done as late as possible before publishing

    • Register an action to be done when a build scan is published

    • Set terms of service params programmatically

    • Set server programmatically

    • Set if untrusted servers are allowed programmatically

    • Specify the publication behaviour programmatically

    • Specify whether goal input files should be captured programmatically

Compatible with scans.gradle.com and Gradle Enterprise 2019.3 or later.

1.1.4

28th June 2019
  • Undeclared inputs are reported correctly on Windows

  • Registering additional inputs/outputs works in the presence of mixed line separators

Compatible with scans.gradle.com and Gradle Enterprise 2019.2 or later.

1.1.3

20th June 2019
  • Add support for upcoming maven-surefire-plugin/maven-failsafe-plugin version 3.0.0-M4

  • Build flags capturing is not based on the MAVEN_CMD_LINE_ARGS environment variable anymore

  • Add support for mojo-executor Maven plugin

  • Failed maven-failsafe tests are no longer cached

Compatible with scans.gradle.com and Gradle Enterprise 2019.2 or later.

1.1.2

17th May 2019
  • Fixed a bug which resulted in wrong event order caused by system clock adjustments

  • Fix handling of ** in include patterns of supported goals and custom input declarations

Compatible with scans.gradle.com and Gradle Enterprise 2019.2 or later.

1.1.1

12th May 2019
  • Read project-specific gradle-enterprise.xml from same .mvn directory that Maven uses to read extensions.xml

Compatible with scans.gradle.com and Gradle Enterprise 2019.2 or later.

1.1

3rd May 2019
  • Output of javadoc:aggregate is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:aggregate-jar is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:aggregate-no-fork is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:test-aggregate is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:test-aggregate-jar is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:test-aggregate-no-fork is cached for maven-javadoc-plugin:2.7+

  • Maven log no longer disappears when extension is applied twice

  • Skipped cacheable goals are reported as "skipped" instead of "not cacheable"

  • Local cache issues fail the build instead of logging a warning

  • Extension only snapshots input files that match given include/exclude patterns

  • Fix NullPointerException when trying to resolve a non-existing plugin without version

  • Parallel forked goal executions are captured correctly in build scans

  • Capture finer-grained fingerprint events

Compatible with scans.gradle.com and Gradle Enterprise 2019.2 or later.

1.0.8

18th April 2019
  • Fix handling of null-valued system properties

  • Prevent non existing javadoc jars from being attached

  • java.io.tmpdir is ignored in surefire systemProperties/systemPropertyVariables

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

1.0.7

17th April 2019
  • Output of javadoc:test-jar is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:jar is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:test-javadoc-no-fork is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:test-javadoc is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:javadoc-no-fork is cached for maven-javadoc-plugin:2.7+

  • Output of javadoc:javadoc is cached for maven-javadoc-plugin:2.7+

  • Output of generate goal is cached for maven-jaxb2-plugin:0.12.3+

  • Output of checkstyle:check is cached for maven-checkstyle-plugin:2.14+

  • Output of checkstyle:checkstyle is cached for maven-checkstyle-plugin:2.14+

  • Output of checkstyle:checkstyle-aggregate is cached for maven-checkstyle-plugin:2.14+

  • Protect against double applications

  • Protect against event notifications received before initialization (workaround for MNG-6619)

  • Handle more absolute paths in JVM arguments out of the box

  • Broken java executables make goals non-cacheable instead of failing the build

  • System properties now overwrite values in gradle-enterprise.xml

  • Allow same file to appear in multiple output locations (e.g. the summaryFile of the maven-failsafe-plugin)

  • User can add additional inputs and outputs to goal executions using the pom.xml DSL

  • Command line arguments are normalized, removing all known input and output paths from them to allow relocation

  • Caching is deactivated for goals that contain undeclared file paths in their input properties (e.g. JVM args)

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

1.0.6

26th March 2019
  • Add support for code completion of publishMode in gradle-enterprise.xml

  • Support broken JARs in annotation processor detection

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

1.0.5

20th March 2019
  • Only fingerprints for external jars are stored in the build cache

  • Surefire statistics file and tempDir are tracked as local state

  • Performance improvements for jar fingerprinting and cache load operations

  • More helpful error message for invalid build scan publishing mode configuration

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

1.0.4

15th March 2019
  • Fix ID generation for build cache events

  • Jar fingerprints are stored in the build cache

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

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

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

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

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

1.0.1

1st March 2019
  • Restore compatibility with Maven < 3.5.2

  • Enhance console output of terms of services

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

1.0

28th February 2019
  • Initial release

Compatible with scans.gradle.com and Gradle Enterprise 2019.1 or later.

Appendix F: 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.

Appendix G: Compatibility with IntelliJ IDEA

No build scans will be published and the cache will not be used when running builds from IDEA versions below 2019.2 or version 2019.3. This is due to issues in these IDEA versions that we can’t work around. We recommend using IDEA 2019.3.1 or above to get the best experience.

Appendix H: Verifying the signature of the extension jar

The extension jar is published to Maven Central alongside its signature (cf. OSSRH Guide). The public key is published to http://pool.sks-keyservers.net and https://keys.openpgp.org. You can verify the signature as follows:

curl -OL https://repo1.maven.org/maven2/com/gradle/gradle-enterprise-maven-extension/1.10.2/gradle-enterprise-maven-extension-1.10.2.jar
curl -OL https://repo1.maven.org/maven2/com/gradle/gradle-enterprise-maven-extension/1.10.2/gradle-enterprise-maven-extension-1.10.2.jar.asc
gpg --keyserver keys.openpgp.org --recv-key 314FE82E5A4C5377BCA2EDEC5208812E1E4A6DB0
gpg --verify gradle-enterprise-maven-extension-1.10.2.jar.asc gradle-enterprise-maven-extension-1.10.2.jar

The output of the last command should look similar to the following:

gpg: Signature made Tue May  5 08:36:01 2020 UTC
gpg:                using RSA key 5208812E1E4A6DB0
gpg: Good signature from "Gradle Inc. <info@gradle.com>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 314F E82E 5A4C 5377 BCA2  EDEC 5208 812E 1E4A 6DB0

This verifies that the artifact was signed with the private key that corresponds to the imported public key. The warning is emitted because you haven’t explicitly trusted the imported key (hence [unknown]). One way of establishing trust is to verify the fingerprint over a secure channel. Please contact technical support should you wish to do so.