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

# Gradle Release Notes

We are excited to announce Gradle 9.4.0 (released 2026-03-04).

Gradle now supports Java 26.

This release significantly improves test reporting and execution by introducing support for non-class-based tests, enabling direct execution of Cucumber features and custom test engines, and capturing richer test metadata directly into HTML reports.

There are notable command-line interface and problem reporting refinements, including high-resolution progress bars with native terminal integration, a more intuitive Problems HTML report, and expanded output formats for the PMD plugin.

This version also enhances build authoring with simplified APIs, improves Configuration Cache debugging with clearer attribution for closures and lambdas, and adds security improvements, including Bearer token authentication for the Gradle Wrapper and automatic cleanup of daemon logs. We've refined plugin authoring by adding default plugin IDs and stricter validation for published plugins.

Finally, tooling integration improvements provide new Tooling API models plus granular control over Tooling API parallelism.

We would like to thank the following community members for their contributions to this release of Gradle: akankshaa-00, Attila Kelemen, Björn Kautler, dblood, Dennis Rieks, duvvuvenkataramana, John Burns, Julian, kevinstembridge, Niels Doucet, Philip Wedemann, ploober, Richard Hernandez, Roberto Perez Alcolea, Sebastian Lövdahl, stephan2405, Stephane Landelle, Ujwal Suresh Vanjare, Victor Merkulov, Vincent Potuček, Vladimir Sitnikov.

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.4.0 by updating the wrapper in your project:

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

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

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

## New features and usability improvements

### Support for Java 26

With this release, Gradle supports Java 26. This means you can now use Java 26 for the daemon in addition to toolchains. Third-party tool compatibility with Java 26 may still be limited.

See the compatibility documentation for more details.

### Test reporting and execution

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

#### Non-class-based testing

When testing using JUnit Platform, Gradle can now discover and execute tests that are not defined in classes.

JUnit Platform `TestEngine`s can discover and execute tests in arbitrary formats, extending testing beyond the confines of JVM classes. In this release, tests can be defined in any format supported by the configured `TestEngine`. Gradle no longer requires a test class to be present to “unlock” test execution.

For example, this library project structure doesn't use typical class-based testing, but instead uses XML test definitions understood by a custom `TestEngine`:

    my-lib/
    ├── src/
    │   ├── main/
    │   │   └── java/
    │   └── test/
    │       └── definitions/
    │           ├── some-tests.xml
    │           ├── some-other-tests.xml
    │           └── sub/
    │               └── even-more-tests.xml
    └── build.gradle.kts

    testing.suites.named("test", JvmTestSuite::class) {
        useJUnitJupiter()

        dependencies {
            implementation("...") // Library containing custom TestEngine
        }

        targets.all {
            testTask.configure {
                testDefinitionDirs.from("src/test/definitions") // Conventional non-class-based test definitions location
            }
        }
    }

This feature works both with and without using JvmTestSuites.

We recommend storing non-class test definitions in the conventional location `src/<TEST_TASK_NAME>/definitions` to keep builds using this feature structured similarly; however, any location can be used.

For more information, see the section on Non-Class-Based Testing in the User Manual.

##### Improved Cucumber support

`TestEngine`s such as Cucumber previously required workarounds when testing with Gradle, such as creating an empty `@Suite` class, or using a JUnit extension like `@RunWith(Cucumber.class)` to satisfy Gradle's class-based test discovery requirement.

These non-class-based tests can now be run directly without workarounds:

    testing.suites.named("test", JvmTestSuite::class) {
        useJUnitJupiter()

        dependencies {
            implementation("io.cucumber:cucumber-java:7.15.0")
            runtimeOnly("io.cucumber:cucumber-junit-platform-engine:7.15.0")
        }

        targets.all {
            testTask.configure {
                testDefinitionDirs.from("src/test/resources")  // Conventional Cucumber *.feature files location
            }
        }
    }

#### Additional test data capture

During test execution, JUnit Platform tests can emit additional data such as file attachments or arbitrary key–value pairs using the TestReporter API.

This data can include metadata about the tests or their environment, or files used or generated during testing, such as screenshots.

For example:

    @Test
    void someTestMethod(TestReporter testReporter) {
        testReporter.publishEntry("myKey", "myValue");
        testReporter.publishFile("screenshot1.svg", MediaType.create("image", "svg+xml"), file -> {});
        // Test logic continues...
    }

Gradle now captures this metadata and integrates it directly into both the HTML test report and the XML test results.

When a test publishes data, the HTML report now features two additional tabs alongside the standard `stdout` and `stderr`:

- Data: Displays custom key–value entries.
- Attachments: Provides links to any published file attachments.

To ensure compatibility with CI/CD pipelines, this data is represented in the XML output as follows:

- `ReportEntry` values are mapped to `<properties/>`.
- `FileEntry` values are formatted as `[[ATTACHMENT|/path/to/file]]`, following established conventions used by Jenkins, Azure Pipelines, and GitLab.

This capture mechanism is comprehensive; it supports both class-based and non-class-based tests and includes data published during test construction as well as setup and teardown phases.

This is especially useful for capturing failure screenshots in UI tests. For file attachments, some known media types, such as images and videos, are rendered directly in the HTML reports. Other file types are presented as links. This can make it easier to diagnose issues without reproducing them locally:

#### Test metadata logging

Test data capture events, as detailed in the previous section, can be observed by Gradle through a new listener dedicated to test metadata events during execution, allowing for more sophisticated tracking of test behavior.

Similar to the existing `TestOutputListener`, you can now register a `TestMetadataListener` to receive structured metadata events emitted by the test framework. This is done via the new `Test#addTestMetadataListener(TestMetadataListener)` method:

    class LoggingListener(val logger: Logger) : TestMetadataListener {
        override fun onMetadata(descriptor: TestDescriptor , event: TestMetadataEvent) {
            logger.lifecycle("Got metadata event: " + event.toString())
        }
    }

    tasks.named<Test>("test").configure {
        addTestMetadataListener(LoggingListener())
    }

This addition enables fuller support for advanced JUnit Platform features. It allows tests to communicate structured information back to the build process, providing a cleaner and more reliable alternative to parsing standard output or error streams. For example, you can use the listener to automatically copy the failure screenshots (from the previous section) to a dedicated CI artifacts directory, upload them to cloud storage for team access, or compress and archive them with timestamp-based naming.

### CLI, logging, and problem reporting

Gradle provides an intuitive command-line interface, detailed logs, and a structured problems report that helps developers quickly identify and resolve build issues.

#### Enhanced terminal progress bars

Gradle’s command-line interface has been updated with progress bars that offer enhanced compatibility for modern terminals:

- Ligature-Safe Rendering: Progress bars no longer interfere with ligature fonts, ensuring a clean visual experience.
- Unicode Support: High-resolution Unicode characters are now used for rendering whenever the terminal supports them.
- Native Terminal Integration: Added support for OSC 9;4 escape sequences. This allows native progress bar display in terminals like Ghostty and iTerm2 >3.6.6.

The progress bars are displayed on terminals that support them:

#### Problems HTML report refinements

The incubating Problems HTML report has been refined to provide a more intuitive and efficient user experience.

To help you find relevant information faster, the report's structure and readability have been optimized:

- Clearer Summaries: The summary clearly displays the number of problems that lack a specific location or were skipped for performance reasons.
- Smart Hierarchy: Each tab loads with collapsed trees for a cleaner initial view, while specific location and solution nodes are expanded by default to reduce the number of clicks needed to see actionable data.
- Logical Sorting: All entries are sorted alphabetically and by location.
- Improved Formatting: Problem details use a monospaced font, ensuring that multi-line messages and stack traces maintain their intended alignment.
- Reduced Noise: Duplicate information has been removed across the report, and the overall file size has been reduced for faster loading.

You can now influence whether a link to the report is printed at the end of a build via the `org.gradle.warning.mode` property. If set to `none`, the report is still generated in the background, but the link is omitted from the build output to keep your console clean.

#### Support for CSV, Code Climate, and SARIF reports in the PMD plugin

The PMD plugin, which performs quality checks on your Java source files, has expanded its reporting capabilities.

In addition to standard XML and HTML, the plugin now supports generating reports in CSV, Code Climate, and SARIF formats. This allows for easier ingestion of PMD results by static analysis platforms and CI/CD security dashboards.

These formats are not enabled by default. To use them, configure the specific `Pmd` task (such as `pmdMain`) rather than the general `pmd` extension:

    // Note that report configuration must be done on the `Pmd` task (here `pmdMain`), not the `pmd` extension.
    tasks.pmdMain {
        reports {
            csv.required = true
            // Optional, defaults to "<project dir>/build/reports/pmd/main.csv"
            csv.outputLocation = layout.buildDirectory.file("reports/my-custom-pmd-report.csv")

            codeClimate.required = true
            // Optional, defaults to "<project dir>/build/reports/pmd/main.codeclimate.json"
            codeClimate.outputLocation = layout.buildDirectory.file("reports/my-custom-codeclimate-pmd-report.json")

            sarif.required = true
            // Optional, defaults to "<project dir>/build/reports/pmd/main.sarif.json"
            sarif.outputLocation = layout.buildDirectory.file("reports/my-custom-sarif-pmd-report.json")
        }
    }

For more information on configuring static analysis, see the PMD plugin documentation.

### Security and infrastructure

Gradle provides robust security features and underlying infrastructure to ensure that builds are secure, reproducible, and easy to maintain.

#### Bearer token authentication for wrapper download

The Gradle Wrapper now supports Bearer token authentication for downloading Gradle distributions from authenticated backends. This provides a modern, secure alternative to Basic authentication (username and password), which was the only method supported in previous versions:

- Authentication Priority: Bearer tokens can be specified via system properties and take precedence over Basic authentication if both are configured.
- Per-Host Configuration: To prevent credential leakage, both Basic and Bearer authentication can now be configured on a per-host basis. This ensures your credentials are only sent to the specific hosts you authorize, rather than being broadcast globally.

While these authentication methods are supported over both HTTP and HTTPS, using a secure HTTPS backend is strongly preferred.

For more details on setup, see the Wrapper documentation.

#### Daemon logging improvements

The Gradle Daemon is a long-lived, persistent process that runs in the background and hosts Gradle’s execution engine. It dramatically reduces build times using caching, runtime optimizations, and eliminating JVM startup overhead.

Gradle Daemon logs older than 14 days are now automatically cleaned up when the daemon shuts down, eliminating the need for manual cleanup.

See the Daemon documentation for more details.

### Build authoring

Gradle provides rich APIs for build engineers and plugin authors, enabling the creation of custom, reusable build logic and better maintainability.

#### `Configuration.extendsFrom` accepts `Provider`s

It is now possible to pass a `Provider<Configuration>` directly when calling `extendsFrom()` on a `Configuration`).

By accepting a `Provider`, Gradle can now establish the relationship without requiring the parent to be realized immediately:

    configurations {
        // dependencyScope creates a Provider<Configuration>
        val parent = dependencyScope("parent")
        val child = resolvable("child") {
            // No .get() required; the relationship is established lazily
            extendsFrom(parent) // previously required 'parent.get()'
        }
    }

### Configuration Cache

Gradle provides a Configuration Cache that improves build time by caching the result of the configuration phase and reusing it for subsequent builds.

#### Improved hit rates for changes in `gradle.properties` files

Previously, changing any `gradle.properties` file resulted in invalidating the Configuration Cache, even if no project properties were changed, or any changed properties weren't used during the configuration phase.

Consider the following Kotlin DSL example:

    tasks.register("printValue") {
        val value = providers.gradleProperty("value").orElse("N/A")
        doLast {
            println("value: ${value.get()}")
        }
    }

When running the `printValue` task with the Configuration Cache enabled, Gradle caches the work graph:

    $ ./gradlew --configuration-cache printValue

    Calculating task graph as no cached configuration is available for tasks: printValue

    > Task :printValue
    value: N/A

    ...
    Configuration cache entry stored.

Previous versions of Gradle were unable to reuse this cache entry when re-executing the `printValue` task after changing anything in `gradle.properties`:

    $ echo "value=1" >> gradle.properties
    $ ./gradlew --configuration-cache printValue

    Calculating task graph as configuration cache cannot be reused because file 'gradle.properties' has changed.

    > Task :printValue
    value: 1

    ...
    Configuration cache entry stored.

In this release, Gradle now detects that only the `value` property was changed, and that this property was never used during the configuration phase. This allows Gradle to reuse the configuration cache entry and start executing tasks faster

    $ echo "value=1" >> gradle.properties
    $ ./gradlew --configuration-cache printValue

    Reusing configuration cache.

    > Task :printValue
    value: 1

    ...
    Configuration cache entry reused.

#### Clearer attribution for closures and lambdas

Identifying the source of Configuration Cache violations can be challenging when a task contains multiple lambdas or closures. Common examples include task actions like `doFirst`/`doLast`, or task predicates such as `onlyIf`, `upToDateWhen`, and `cacheIf`/`doNotCacheIf`.

Previously, if one of these closures captured an unsupported type (such as a reference to the enclosing script), the problem report was often ambiguous:

    fun myFalse() = false

    fun noOp() { }

    tasks.register("myTask") {
        outputs.cacheIf { myFalse() }
        outputs.doNotCacheIf("reason") { myFalse() }
        outputs.upToDateWhen { myFalse() }
        onlyIf { myFalse() }
        doLast { noOp() }
    }

In earlier versions, the report would reference a cryptic generated class name, leaving you to guess which specific block was the culprit:

Starting with this release, the Configuration Cache report now explicitly identifies the type of action or spec associated with each lambda. This provides the necessary context to pinpoint and fix the violation immediately:

### Core plugin and plugin authoring

Gradle provides a comprehensive plugin system, including built-in Core Plugins for standard tasks and powerful APIs for creating custom plugins.

#### Default plugin IDs

This release reduces the boilerplate required for plugin authors when using the `java-gradle-plugin` plugin by introducing a sensible default for plugin IDs.

Previously, you had to explicitly provide a string for both the registration name and the `id` property. Now, the plugin ID is automatically set to the registration name by default:

    gradlePlugin {
        plugins {
            register("my.plugin-id") {
                // id is automatically inferred as "my.plugin-id"
                implementationClass = "my.PluginClass"
            }
        }
    }

This change makes your build scripts cleaner and less repetitive, especially in projects that define multiple plugins. If you still need a custom ID that differs from the registration name, the `id` property remains available for manual overrides.

For more details, check out the Java Gradle Plugin documentation.

#### Stricter validation for published plugins

To ensure high quality and compatibility across the plugin ecosystem, Gradle now automatically enables stricter validation for projects that use the `com.gradle.plugin-publish`, `ivy-publish`, or `maven-publish` plugins.

This validation catches common issues, such as missing task input annotations or improper property definitions, before a plugin is distributed.

To avoid breaking your internal builds, this automatic enforcement does not apply to local plugins (e.g., those in `buildSrc` or included builds).

While only enabled by default for publishing, we recommend opting into stricter validation for all plugin projects to ensure they are robust and future-proof. You can enable it manually in your build script:

    tasks.validatePlugins {
        enableStricterValidation = true
    }

### Tooling and IDE integration

Gradle provides Tooling APIs that facilitate deep integration with modern IDEs and CI/CD pipelines.

#### New property for Tooling API parallelism control

Gradle now provides granular control over how Tooling API clients interact with your build in parallel using a new `org.gradle.tooling.parallel` property.

Previously, parallelism for Tooling API actions was tied directly to the `org.gradle.parallel` property. This meant that if you wanted to enable parallel task execution, you were forced to also enable parallel IDE actions, and vice versa.

The new property decouples these two behaviors. This is particularly relevant for the IDE Sync scenarios, where IDEs can take advantage of the parallelism to improve performance (independently of your task execution strategy):

    # gradle.properties

    // Controls parallelism for Tooling API clients (e.g., IDE Sync)
    org.gradle.tooling.parallel=true

    // Controls parallelism for task execution (e.g., build/test)
    org.gradle.parallel=false

When `org.gradle.tooling.parallel` is not specified, it defaults to the value of `org.gradle.parallel`, preserving existing behavior and performance characteristics. For more information, see the Tooling API parallelism configuration section of the user guide.

#### Tooling integration improvements

This release adds a few enhancements to the built-in Tooling API models:

- Instant Version Info: Clients can now access the exact output of `gradle --version` without starting a daemon, via the new `BuildEnvironment.getVersionInfo()` property.
- Built-in Help Model: A new `Help` model exposes the output of the `gradle --help` command-line build invocation.

The following example demonstrates how to retrieve version and help information using a `ProjectConnection`:

    import org.gradle.tooling.GradleConnector;
    import org.gradle.tooling.ProjectConnection;
    import org.gradle.tooling.model.build.BuildEnvironment;
    import org.gradle.tooling.model.build.Help;

    import java.io.File;

    void main() {
        var projectDir = new File("/path/to/project");
        try (var conn = GradleConnector.newConnector().forProjectDirectory(projectDir).connect()) {
            System.out.println("--version:\n + " + conn.getModel(BuildEnvironment.class).getVersionInfo());
            System.out.println("--help:\n" + conn.getModel(Help.class).getRenderedText());
        }
    }

## 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.

- `getSettingsDirectory()` in `ProjectLayout`

### Task graph is now stable

The task graph, introduced as an incubating feature in Gradle 9.1.0, is now stable. It is no longer marked as experimental.

## Documentation and training

### Documentation

#### User Manual

A brand-new section of the User Manual has been started, called Securing Your Gradle Builds.

#### Best Practices

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

- Prefer the -bin Gradle Distribution

### Training

The following course is now available:

- Authoring Gradle Plugins

## 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.