Gradle 9.3.1 Release Notes
Description: We are excited to announce Gradle 9.3.1.

# Gradle Release Notes

Gradle 9.3.1 has been released (2026-01-29).

This patch release includes bug fixes and improvements.

The following issues were resolved:

- Cannot find testcases from Android Screenshot Test plugin since Gradle 9.3.0
- Excluding dependencies from included builds doesn't work in Gradle 9.3.0
- ExternalDependency and DependencyConstraint cannot be passed to DependencyResolveDetails#useTarget
- Gradle 9.3.0 generate JUnit test result files with wrong name
- Build cache cannot handle outputs with non-BMP characters in the filename
- Emojis in test names should not break build caching
- Non utf-8 c code is no longer buildable
- Breaking change in 9.3.0 regarding cross-project dependency manipulation
- JUnit3 tests cannot be run with Gradle 9.3.0
- Test.setScanForTestClasses(false) causes all junit4 tests to be skipped

We recommend upgrading to Gradle 9.3.1.

This release brings test reporting improvements, including a more detailed HTML test report for nested, parameterized, and suite-based tests, better aggregate reporting, and a new streaming API in TestKit for efficiently reading build output.

It also enhances build authoring with a new `AttributeContainer.named()` convenience method for more concise attribute configuration.

There are error and warning reporting improvements, with Problems API reports now rendered in the console when using `--warning-mode=all`.

Finally, this release addresses two security vulnerabilities:

- Failure to disable repositories with an unknown host can expose builds to malicious artifacts
- Failure to disable repositories failing to answer can expose builds to malicious artifacts

Review the advisories above for mitigation strategies if an immediate upgrade is not feasible.

We would like to thank the following community members for their contributions to this release of Gradle: Adam, Adam, Aharnish Solanki, Andrzej Zabost, Björn Kautler, Boris Petrov, Jendrik Johannes, Kamil Krzywanski, KANAKALA SAI KIRAN, Megmeehey, NurmukhametovAlexey, Philip Wedemann, Piotr Kubowicz, Samay Kumar, Shin Minjun, Stefan Oehme, Vincent Potuček, Yongshun Ye.

Be sure to check out the public roadmap for insight into what's planned for future releases.

## Table Of Contents

## Upgrade instructions

Switch your build to use Gradle 9.3.1 by updating the wrapper in your project:

    ./gradlew wrapper --gradle-version=9.3.1 && ./gradlew wrapper

See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes, and other considerations when upgrading to Gradle 9.3.1.

For Java, Groovy, Kotlin, and Android compatibility, see the full compatibility notes.

## New features and usability improvements

### Test reporting improvements

Gradle provides a rich set of features and abstractions for testing JVM code, along with test reports to display results.

#### Test results reporting

The HTML test report generated by the `test` task, `TestReport`, and other `AbstractTestTask` usages now presents test results in a clearer structure that reflects how tests are defined:

This results from Gradle adopting the incubating Test Event Reporting API internally.

In the following sections, "Suite" refers specifically to the suite features of JUnit 4, JUnit Jupiter, and TestNG while "Container" refers to a general grouping of tests, such as a class.

##### Nested test changes

For JUnit 4 and JUnit Jupiter, nested test classes are now shown nested under their enclosing class in the HTML Test Report.

For example, if you had an `OuterClass` with an `InnerClass` nested inside it, it would previously be shown as:

    + OuterClass$InnerClass
    |-- someTestMethodInInnerClass

It will now be reported in the HTML Test Report as:

    + OuterClass
    |-+ InnerClass (or OuterClass$InnerClass for JUnit 4)
      |-- someTestMethodInInnerClass

With multiple inner classes:

    + OuterClass
    |-+ InnerClass1 (or OuterClass$InnerClass1 for JUnit 4)
      |-- someTestMethodInInnerClass1
      + InnerClass2 (or OuterClass$InnerClass2 for JUnit 4)
      |-- someTestMethodInInnerClass2

The XML report remains the same, nested classes are still written as `TEST-OuterClass$InnerClass.xml`.

##### Parameterized test changes

Parameterized tests now create a suite that contains all the parameterized test cases for a given method.

For example, if you had a class `TestClass` with two parameterized `paramTest1/2` methods, it would previously be shown as:

    + TestClass
    |-- paramTest1[0]
    |-- paramTest1[1]
    |-- paramTest1[2]
    |-- paramTest2[a]
    |-- paramTest2[b]
    |-- paramTest2[c]

It will now be reported in the HTML Test Report as:

    + TestClass
    |-+ paramTest1
      |-- paramTest1[0]
      |-- paramTest1[1]
      |-- paramTest1[2]
    |-+ paramTest2
      |-- paramTest2[a]
      |-- paramTest2[b]
      |-- paramTest2[c]

##### Suite changes

Suites now contain the classes they run, rather than those classes being shown as siblings.

For example, if you had a suite `AllTests` that ran `TestClass1` and `TestClass2`, it would previously be shown as:

    + AllTests
    + TestClass1
    |-- someTestMethodInClass1
    + TestClass2
    |-- someTestMethodInClass2

It will now be reported in the HTML Test Report as:

    + AllTests
    |-+ TestClass1
      |-- someTestMethodInClass1
    |-+ TestClass2
      |-- someTestMethodInClass2

In the XML report, only the class report is emitted (`TEST-SomeTestClass.xml`).

##### Package suite changes

Packages are no longer represented as containers in the HTML report.

For example, if you had classes org.example.FirstTest and org.example.SecondTest, it would previously be shown as:

    + org.example
      |-- FirstTest
      |-- SecondTest

It will now be reported in the HTML Test Report as:

    + org.example.FirstTest
    + org.example.SecondTest

There are two main reasons for this change:

1. With support for non-class-based testing, Gradle cannot reliably determine if a container corresponds to a class, so synthesizing a package container can be misleading.
2. This behavior now aligns more closely with how testing frameworks and IDEs group tests.

##### Test standard output/error changes

Standard output and standard error (collectively, "output") are no longer combined from individual tests to the container/class level. Instead, the output stays attached to the individual test that produced it:

This makes it easier to locate output that is relevant to a specific test.

`@Before` and `@After` class output is now associated with the correct class in JUnit 4, JUnit Jupiter, and TestNG (starting with TestNG 6.9.13.3). `@Before` and `@After` suite or container output is now associated with the correct suite or container for JUnit 4 (starting with JUnit 4.13), JUnit Jupiter, and TestNG (starting with TestNG 6.19.13.3):

#### Aggregate report changes

The Test Report Aggregation Plugin provides tasks and configurations used to aggregate the results of multiple Test task invocations into a single HTML report.
This report, which can also be generated manually with `TestReport`, now supports overlapping test structures.

For example, when a suite with the same name exists in multiple subprojects:

- `application` subproject has an `AdderTest`
- `direct` subproject has an `AdderTest` and a `MultiplierTest`
- `transitive` subproject has an `AdderTest` (which fails) and a `PowerizeTest`

Previously, the report looked like this when a failure occurred:

Now, each individual report source is represented as a separate tab. To see the tests from a specific source, select its corresponding tab:

### Error and warning reporting improvements

Gradle provides a rich set of error and warning messages to help you understand and resolve problems in your build.

#### Simple console rendering for Problem Reports

The Problems API provides structured feedback on build issues, helping developers and tools like IDEs identify and resolve warnings, errors, or deprecations during configuration or runtime.

Previously, a limitation was that the problem report was linked to in the console output, but the problems themselves were not displayed:

    $ ./gradlew :test --warning-mode=all

    > Configure project :
    [Incubating] Problems report is available at: file:///Users/user/test-report-aggregation-sample/build/reports/problems/problems-report.html

In this release, we've added basic console integration. Relevant problems in the report are now rendered in the console output when you use `--warning-mode=all`:

    $ ./gradlew :test --warning-mode=all

    > Configure project :
    Build file '/Users/user/Downloads/test-report-aggregation-sample/build.gradle': line 16
    The Wrapper.getAvailableDistributionTypes method has been deprecated...
            at build_5teuix0v7qf7ou93kgnmvnicp.run(/Users/user/test-report-aggregation-sample/build.gradle:16)
            (Run with --stacktrace to get the full stack trace of this deprecation warning.)
    [Incubating] Problems report is available at: file:///Users/user/test-report-aggregation-sample/build/reports/problems/problems-report.html

#### Clearer explanation for worker process exit codes

Gradle now provides a short explanation when a worker process exits with a code that typically indicates it was killed by the operating system.

Previously, you would see:

    > Process 'Gradle Worker Daemon 87' finished with non-zero exit value 137

Now, Gradle adds a helpful hint based on the value of the exit code:

    > Process 'Gradle Worker Daemon 87' finished with non-zero exit value 137
    (this value may indicate that the process was terminated with the SIGKILL
    signal, which is often caused by the system running out of memory)

This makes it easier to understand failures caused by conditions such as running out of memory.

### Build authoring improvements

Gradle provides rich APIs for plugin authors and build engineers to develop custom build logic.

#### New `AttributeContainer.named()` method

An `AttributeContainer` stores attributes that Gradle uses to pick the right variant of a dependency during resolution.
This release introduces the new convenience method on `AttributeContainer.named()`.
This method can create attribute values directly from the container without requiring the use of the `ObjectFactory`.

This method makes attribute assignment more concise while preserving the same semantics as creating a named value via the `ObjectFactory`:

    configurations.resolvable("foo") {
        attributes {
            // Before:
            attribute(Usage.USAGE_ATTRIBUTE, objects.named("red"))

            // After:
            attribute(Usage.USAGE_ATTRIBUTE, named("red"))
        }
    }

#### Stream TestKit output

In Gradle’s TestKit, `BuildResult` is the object that represents the outcome of a test build you ran with GradleRunner.
`BuildResult` now offers a new method for accessing the build console output efficiently, especially for builds that produce a large volume of logs.

`BuildResult.getOutput()` returns a `String` with the full build console output.
This can use large amounts of memory for builds with extensive logs.

A new `BuildResult.getOutputReader()` method is available, returning a `BufferedReader` for streaming the build output incrementally. This can help reduce memory pressure in TestKit tests.

Ensure you close the `BufferedReader` after use.
We recommend the standard Java try-with-resources pattern for this:

    void testProject() {
        BuildResult buildResult = GradleRunner.create()
            .withProjectDir(File("test-project"))
            .withArguments(":build", "--info")
            .build();

        try (BufferedReader outputReader = buildResult.getOutputReader()) {
            List<String> logLines = outputReader.lines()
                .filter(line -> line.contains("example build message"))
                .collect(Collectors.toList());
            // do something with the log lines...
        }
    }

### Publishing signatures for distributions

Gradle now publishes ASCII-armored `.asc` signature files for all distribution ZIPs, alongside the existing `.sha256` checksum files.

These PGP signatures allow users and build systems to verify that a downloaded Gradle distribution was produced by the Gradle team and has not been tampered with, improving supply-chain integrity and enabling stronger verification workflows.

For details on verifying Gradle distributions and JARs, see the Signing key for Gradle artifacts page.

### Dependency repositories can be disabled and skipped for more reasons

When Gradle fails to retrieve information from a repository after multiple retries or specific fatal errors, the repository will no longer be checked for the remainder of the build. This will usually fail dependency resolution because Gradle will not continue to the next repository in the list unless told otherwise.

This behavior ensures reproducibility.

In this release, more failures will cause the repository to be disabled, such as an incorrect hostname.

See the documentation for details, including ways to continue resolution even if a repository is disabled.

## Promoted features

Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to backward compatibility. See the User Manual section on the "Feature Lifecycle" for more information.

The following are the features that have been promoted in this Gradle release.

- `useFileSystemPermissions()` in `AbstractArchiveTask`

## Documentation and training

### User Manual

The Plugin Development section has been updated with clearer explanations and coded examples of init scripts and Init Plugins, including when to use each.

### Best Practices

The following best practices have been added in this Gradle release:

- Use Convention Plugins for Common Build Logic
- Validate Gradle wrapper JAR checksum

### Training

The following course is now available:

- Introduction to Gradle for Build Engineers

The following learning path is now available:

- Introduction to Gradle

## Fixed issues

## Known issues

Known issues are problems that were discovered post-release that are directly related to changes made in this release.

## External contributions

We love getting contributions from the Gradle community. For information on contributing, please see gradle.org/contribute.

## Reporting problems

If you find a problem with this release, please file a bug on GitHub Issues adhering to our issue guidelines. If you're not sure if you're encountering a bug, please use the forum.

We hope you will build happiness with Gradle, and we look forward to your feedback via Twitter or on GitHub.