Gradle Release Notes

The Gradle team is excited to announce Gradle 8.8-rc-1.

Gradle now supports Java 22.

This release introduces a preview feature to configure the Gradle daemon JVM using toolchains and improved IDE performance with large projects.

Additionally, this release includes many notable improvements to build authoring, error and warning messages, the build cache, and the configuration cache.

We would like to thank the following community members for their contributions to this release of Gradle: Björn Kautler, Denes Daniel, Fabian Windheuser, Hélio Fernandes Sebastião, Jay Wei, jhrom, jwp345, Jörgen Andersson, Kirill Gavrilov, MajesticMagikarpKing, Maksim Lazeba, Philip Wedemann, Robert Elliot, Róbert Papp, Stefan M., Tibor Vyletel, Tony Robalik, Valentin Kulesh, Yanming Zhou, 김용후

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 8.8-rc-1 by updating your wrapper:

./gradlew wrapper --gradle-version=8.8-rc-1

See the Gradle 8.x upgrade guide to learn about deprecations, breaking changes, and other considerations when upgrading to Gradle 8.8-rc-1.

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

New features and usability improvements

Full Java 22 support

With this release, Gradle supports running on Java 22. This means you can now use Java 22 for the daemon in addition to toolchains.

For details, see the full compatibility documentation.

Gradle daemon JVM configurable via toolchain

Previously, Gradle did not support capturing the JVM requirements of the build, which could lead to build failures when using the wrong JVM version. This made such builds harder to import into IDEs or run locally.

With this release, users can configure the JVM used to run a Gradle build. This feature is built on top of Java toolchains and works similarly to how the Gradle wrapper captures Gradle version requirements.

This is an incubating feature that will change in future releases.

Improved IDE performance for large projects

When invoking large builds from an IDE, the Tooling API's execution of extensive task graphs suffered from a performance penalty caused by transferring unnecessary information.

Eliminating this transfer results in performance improvements of up to 12% in large up-to-date builds with over 15,000 tasks in their task graph.

We want to thank a community member for identifying and fixing this issue.

Updating your Gradle version will immediately benefit Android Studio, IntelliJ IDEA, Eclipse, and other Tooling API clients.

Build authoring improvements

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

Allow version catalog plugin aliases without a version

Previously, a version catalog plugin alias could be defined without a version, but attempting to use it would result in an exception. It is now explicitly allowed to have a plugin alias with no version, and no exception will be thrown when using it:

# In libs.versions.toml
[plugins]
myPlugin = { id = "my.plugin.id" }
// In build.gradle(.kts)
plugins {
   alias(libs.plugins.myPlugin)
}

Accessors for Settings extensions in Kotlin DSL

Previously, extensions registered in Plugin<Settings> weren't available in settings.gradle.kts. Now, type-safe accessors for these extensions are generated.

interface MySettingsExtension {
    val myProperty: Property<Int>
}

Assuming you register the extension above as mySettingsExtension, then you can access it directly:

// In settings.gradle.kts

// accessor function
mySettingsExtension {
    myProperty = 42
}

// accessor property
println(mySettingsExtension.myProperty)

This fixes a long-standing issue.

Ability to set conventions on file collections

Plugin-provided tasks often expose file collections that build engineers need to customize, such as the classpath for the JavaCompile task. Until now, plugin authors defined default values for these collections by setting initial values.

With this release, Gradle introduces a more flexible approach using conventions. Conventions allow plugin authors to recommend default values, which users can then accept, extend, or completely replace.

This release introduces a pair of convention(...) methods on ConfigurableFileCollection that define the default value of a file collection if no explicit value is previously set via setFrom(...) or from(...):

val files = objects.fileCollection().convention("dir1")
files.from("dir2")

println(files.elements.get()) // [.../dir1, .../dir2]

#from(...) will honor the convention if one is configured when invoked, so the order of operations will matter.

To forcefully override or prevent a convention (i.e., regardless of the order of those operations), use `#setFrom():

val files = objects.fileCollection().convention("dir1")
files.setFrom("dir2")

println(files.elements.get()) // [.../dir2]

This is analogous to the convention(...) methods that have been available on lazy properties since Gradle 5.1.

Updating lazy property based on its current value with replace()

Modify a property based on its current value, such as appending text. previously required obtaining the current value explicitly by calling Property.get():

val property = objects.property<String>()
property.set("some value")
property.set("${property.get()} and more")

println(property.get()) // "some value and more""

This could lead to performance issues like configuration cache misses.

Additionally, when attempting to construct a value lazily, for instance, using property.set(property.map { "$it and more" }), a build failure could occur due to circular reference evaluation.

Property and ConfigurableFileCollection now provide their respective replace(Transformer<...>) methods that allow lazily building the new value based on the current one:

val property = objects.property<String>()
property.set("some value")
property.replace { it.map { "$it and more" } }

println(property.get()) // "some value and more"

Refer to the Javadoc for Property.replace(Transformer<>) and ConfigurableFileCollection.replace(Transformer<>) for more details, including limitations.

New Gradle lifecycle callbacks

This release introduces a new GradleLifecycle API, accessible via gradle.lifecycle, which plugin authors and build engineers can use to register actions to be executed at certain points in the build lifecycle.

Actions registered as GradleLifecycle callbacks (currently, beforeProject and afterProject) are isolated, running in an isolated context that is private to every project. This will allow Gradle to perform additional performance optimizations and will be required in the future to take advantage of parallelism during the build configuration phase.

While the existing callbacks continue to work, we encourage everyone to adopt the new API and provide us with early feedback.

The example below shows how this new API could be used in a settings script or settings plugins:

// settings.gradle
rootProject.name = 'root'

def start = 0
gradle.lifecycle.beforeProject {
    start = System.currentTimeMillis()
}
gradle.lifecycle.afterProject { project ->
    def end = System.currentTimeMillis()
    def elapsed = end - start
    println "Project $project.name configured in ${elapsed}ms"
}

Isolated project views

There is now support for obtaining an isolated view of a project as an IsolatedProject via Project.getIsolated().

The view exposes only those properties that are safe to access across project boundaries when running the build configuration phase in parallel (to be supported in a future release).

The example below shows how the API could be used from a Project configuration callback to query the root project directory in a parallel-safe way:

gradle.lifecycle.beforeProject { project ->
   def rootDir = project.isolated.rootProject.projectDirectory
   println "The root project directory is $rootDir"
}

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.

Improved error handling for toolchain resolvers

This update improves how Gradle handles errors when downloading Java toolchains from configured resolvers.

Previously, if a resolver threw an exception while mapping toolchain specs to download URLs or during the auto-provisioning process, Gradle did not try other configured resolvers. Gradle will now handle these errors better by attempting to use other resolvers in such cases.

Improved JVM version mismatch error reporting

When depending on a library that requires a higher version of the JVM runtime than is requested via the automatically supplied TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE attribute, or when applying a plugin that requires a higher version or the JVM runtime than the current JVM supplies, dependency resolution will fail.

The error message in this situation will now clearly state the issue:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'example'.
> Could not determine the dependencies of task ':consumer:compileJava'.
 > Could not resolve all task dependencies for configuration ':consumer:compileClasspath'.
    > Could not resolve project :producer.
      Required by:
          project :consumer
       > project :producer requires at least a Java 18 JVM. This build uses a Java 17 JVM.

* Try:
> Run this build using a Java 18 JVM (or newer).
> Change the dependency on 'project :producer' to an earlier version that supports JVM runtime version 17.

The failure’s suggested resolutions will include upgrading your JVM or downgrading the version of the dependency.

This replaces the previous low-level incompatibility message, which was difficult to understand without knowledge of Gradle internals. This new error message can be especially helpful for users upgrading the Spring Boot dependencies or the Spring Boot Gradle plugin to version 3+, which requires Java 17 or later.

Fixed error message when buildscript dependencies fail to resolve

When a build script fails to resolve dependencies on its classpath, the error message will now more clearly state the issue:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'unified-prototype'.
> Could not resolve all dependencies for configuration ':classpath'.
  > Could not resolve project :unified-plugin:plugin-android.
    ...

Previously, the error message contained a null and a possibly misleading reference to "task dependencies":

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring root project 'unified-prototype'.
> Could not determine the dependencies of null.
  > Could not resolve all task dependencies for configuration ':classpath'.
     > Could not resolve project :unified-plugin:plugin-android.
       ...

Fixed error reporting when repositories are disabled

When Gradle determines that a particular repository is unavailable when requesting a dependency, it will stop trying to resolve any dependencies from that repository. This can prevent other dependencies from resolving successfully.

The new error message will now print the underlying cause of the issue:

* What went wrong:
A problem occurred configuring root project 'fevi6'.
> Could not resolve all artifacts for configuration ':classpath'.
  > Could not resolve group:a:1.0.
    Required by:
        project :
     > Could not resolve group:a:1.0.
        > Could not get resource 'http://127.0.0.1:49179/repo/group/a/1.0/a-1.0.pom'.
           > Could not GET 'http://127.0.0.1:49179/repo/group/a/1.0/a-1.0.pom'.
              > Read timed out
           ...

Previously, this could result in an error message that only mentioned that dependencies failed to resolve due to "Skipped to earlier error", without printing the error itself:

* What went wrong:
A problem occurred configuring root project 'fevi6'.
> Could not resolve all artifacts for configuration ':classpath'.
  > Could not resolve group:a:1.0.
    Required by:
        project :
     > Skipped due to earlier error
  > Could not resolve group:b:1.0.
    Required by:
        project :
     > Skipped due to earlier error
  > Could not resolve group:c:1.0.
    Required by:
        project :
     > Skipped due to earlier error
  ...

Suppressed duplicate error reporting when multiple failures have the same cause

When multiple failures have the same cause, Gradle will now only print the first failure, which includes all the necessary details.

Any additional failures stemming from the same cause will be summarized at the end of the message, indicating how many were found:

* What went wrong:
Execution failed for task ':resolve'.
> Could not resolve all files for configuration ':deps'.
  > Could not resolve group:a:1.0.
    Required by:
        project : > group:d:1.0
     > <SOME FAILURE CAUSE>
> There are 2 more failures with identical causes.

IDE Integration improvements

Gradle is integrated into many IDEs using the Tooling API.

The following improvements are for IDE integrators. They will become available to end-users in future IDE releases once IDE vendors adopt them.

Tests metadata improvements in Tooling API

IDEs and other tools leverage the tooling API to access information about tests executed by Gradle. Each test event sent via the tooling API includes a test descriptor containing metadata such as a human-readable name, class name, and method name.

Previously, the test display name could only be obtained by parsing the operation display name, which was not always reliable. A new method to the TestOperationDescriptor interface called getTestDisplayName provides the test display name.

For JUnit5 and Spock, we updated the test descriptor for dynamic and parameterized tests to include information about the class name and method name containing the test. These enhancements enable IDEs to offer improved navigation and reporting capabilities for dynamic and parameterized tests.

Build cache changes

The Gradle build cache is a mechanism designed to save time by reusing local or remote outputs from previous builds.

Improved control of local build cache cleanup

With this release, local build cache cleanup is configurable via the standard init-script mechanism, providing improved control and consistency.

//init.gradle.kts
beforeSettings {
   caches {
 cleanup = Cleanup.ALWAYS
       buildCache.setRemoveUnusedEntriesAfterDays(30)
   }
}

Previously, the retention period was configured via the DirectoryBuildCache.removeUnusedEntriesAfterDays setting. See the upgrade guide for details on how to adopt the new API.

Groovy build script compilation build cache support is disabled

In Gradle 8.7, we introduced support for using the remote build cache with Groovy build script compilation. However, after receiving reports of slower compile times with the remote cache, we conducted further investigation. Our findings showed that the cache was not delivering the expected performance improvements.

As a result, we have disabled the remote build cache for Groovy build script compilation in this release.

Configuration cache improvements

The configuration cache improves build time by caching the result of the configuration phase and reusing it for subsequent builds. This feature can significantly improve build performance.

Support for Java Record classes and Externalizable instances

The configuration cache now supports:

Other improvements

Allow specifying source encoding for Jacoco report tasks

For the JaCoCo Plugin, the source encoding of JacocoReport tasks may now be specified via the sourceEncoding property:

jacocoTestReport {
   sourceEncoding = 'UTF-8'
}

Filter standard output and error output in XML test reports

When testing Java projects using common frameworks, reports are typically produced in XML format. The new includeSystemOutLog and includeSystemErrLog options control whether output written to standard output and standard error output during testing is included in those XML test reports. This report format is used by JUnit 4, JUnit Jupiter, and TestNG, despite the name of the report format, and can be configured when using any of these test frameworks.

Disabling these options can be useful when running a test task, as they result in a large amount of standard output or standard error data that is irrelevant to testing. It is also useful for preserving disk space when running jobs on CI.

You can set these options by configuring the JUnitXmlReport options block:

tasks.test {
   reports.junitXml {
       includeSystemOutLog = false
       includeSystemErrLog = true
   }
}

Setting custom POM values in Maven publications for plugins

The Maven-publish plugin provides a way to set custom POM values for a Gradle plugin publication, as demonstrated in the following example:

gradlePlugin {
   plugins {
       register("some.plugin") {
           name = "SomePluginName"
           description = "SomePluginDesc"
       }
   }
}

publishing {
   publications.withType<MavenPublication> {
       pom {
           name = "CustomPublicationName"
           description = "CustomPublicationDesc"
       }
   }
}

Previously, this was not working as expected, and the published POM would contain the name and description values configured via the plugins block.

Now, any values configured in the pom block will take precedence if present and be written to the published POM for that publication:

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <name>CustomPublicationName</name>
 <description>CustomPublicationDesc</description>
 ...
</project>

This fixes a long-standing issue.

Custom dependencies blocks

Since Gradle 8.7, it's been possible for plugins to define their own dependencies-like block. A custom dependencies block allows users to declare dependencies in a type-safe and context-aware way:

// ExamplePlugin.java
public class ExamplePlugin implements Plugin<Project> {
    @Override
    public void apply(Project project) {
        ExampleExtension example = project.getExtensions().create("example", ExampleExtension.class);
    }
}
// build.gradle.kts
example {
    dependencies {
        implementation("junit:junit:4.13")
    }
}

This is currently used by the JVM test suite plugin.

See the user manual to learn more about using this incubating feature.

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.

File Permissions API is now stable

The File Permissions API for defining file permissions using UNIX style values (added in Gradle 8.3) is now stable; see:

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