The Gradle team is excited to announce Gradle 8.8.
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.
Switch your build to use Gradle 8.8 by updating your wrapper:
./gradlew wrapper --gradle-version=8.8
See the Gradle 8.x upgrade guide to learn about deprecations, breaking changes, and other considerations when upgrading to Gradle 8.8.
For Java, Groovy, Kotlin, and Android compatibility, see the full compatibility notes.
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.
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.
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.
Gradle provides rich APIs for plugin authors and build engineers to develop custom build logic.
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)
}
Settings
extensions in Kotlin DSLPreviously, 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.
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.
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 to apply configuration to all projects, while avoiding cross-project configuration:
// settings.gradle.kts
include("sub1")
include("sub2")
gradle.lifecycle.beforeProject {
apply(plugin = "base")
repositories {
mavenCentral()
}
}
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 {
val rootDir = project.isolated.rootProject.projectDirectory
println("The root project directory is $rootDir")
}
Gradle provides a rich set of error and warning messages to help you understand and resolve problems in your build.
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.
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.
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.
...
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
...
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.
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.
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.
The Gradle build cache is a mechanism designed to save time by reusing local or remote outputs from previous builds.
With this release, local build cache cleanup is configurable via the standard init-script mechanism, providing improved control and consistency.
Cleanup.DISABLED
or Cleanup.ALWAYS
will now prevent or force the cleanup of the local build cacheSettings.caches.buildCache.setRemoveUnusedEntriesAfterDays()
//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.
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.
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.
Externalizable
instancesThe configuration cache now supports:
For the JaCoCo Plugin, the source encoding of JacocoReport
tasks may now be specified via the sourceEncoding
property:
jacocoTestReport {
sourceEncoding = 'UTF-8'
}
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
}
}
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.
dependencies
blocksSince 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.
The File Permissions API for defining file permissions using UNIX style values (added in Gradle 8.3) is now stable; see:
Known issues are problems that were discovered post-release that are directly related to changes made in this release.
We love getting contributions from the Gradle community. For information on contributing, please see gradle.org/contribute.
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.