We are excited to announce Gradle 9.7.0-milestone-3 (released 2026-07-10).
This release enhances the Configuration Cache: ResolutionResult can now be included directly as a task input, and source dependencies resolved from version control repositories become fully cache-compatible.
Isolated Projects graduates from experimental to incubating with stable org.gradle.isolated-projects property names, and introduces three modes — fail-fast, diagnostics, and dangerously-ignore-problems — to guide teams through adoption.
Test reporting and execution now surfaces framework-initialization failures for TestNG, JUnit 4, and JUnit Platform in the console by default, and TestNG's threadPoolFactoryClass gains support for TestNG 7.10 and later.
The CLI, logging, and problem reporting captures source locations for up to 2000 more problems past the existing 50-problem cap, so the console, problems report, and Tooling API pinpoint many more issues.
Build authoring gains a reproducibleFileTimestamp property on archive tasks for SOURCE_DATE_EPOCH-compatible builds, along with new getAttributes() and getCapabilities() accessors on ResolvedArtifactResult.
For platform and toolchain management, DomainObjectCollection.getElements() returns a new lazy Provider that bridges the Domain Object Collection and Provider APIs, and Groovydoc gains Java-toolchain support.
Security and infrastructure improvements let dependency verification carry origin and reason attributes on trusted PGP keys, and report how many other keys you already trust for a failing artifact — making key rotations easier to distinguish from first-time trusts.
Finally, general improvements let file system watching work with a custom project cache directory, and drop Kotlin DSL accessor generation from the Build Cache.
We would like to thank the following community members for their contributions to this release of Gradle: Adam, Aman Gautam, Aman Kumar, Aurimas, gbhavya07, Josh Friend, nicklauslittle-gov, Pragati, project516, Ravi, sk-reddy17, Suvrat Acharya, Yongshun Ye.
Be sure to check out the public roadmap for insight into what's planned for future releases.
Switch your build to use Gradle 9.7.0-milestone-3 by updating the wrapper in your project:
./gradlew :wrapper --gradle-version=9.7.0-milestone-3 && ./gradlew :wrapper
See the Gradle 9.x upgrade guide to learn about deprecations, breaking changes, and other considerations when upgrading to Gradle 9.7.0-milestone-3.
For Java, Groovy, Kotlin, and Android compatibility, see the full compatibility notes.
Gradle provides a Configuration Cache that improves build time by caching the result of the configuration phase and reusing it for subsequent builds.
ResolutionResult is fully Configuration Cache compatibleA ResolutionResult may now be included directly as a task input when using Configuration Cache. Previously, its root ResolvedComponentResult and ResolvedVariantResult needed to be extracted and included on the task separately. This provides easy access to convenience APIs on ResolutionResult, avoiding the need to traverse the graph manually to access them.
// Before
abstract class PreviousTask : DefaultTask() {
@get:Input
abstract val rootComponent: Property<ResolvedComponentResult>
@get:Input
abstract val rootVariant: Property<ResolvedVariantResult>
@TaskAction
fun execute() {
// No access to APIs on ResolutionResult, requiring manual graph traversal.
}
}
tasks.register<PreviousTask>("before") {
rootComponent = configurations.runtimeClasspath.flatMap { it.incoming.resolutionResult.rootComponent }
rootVariant = configurations.runtimeClasspath.flatMap { it.incoming.resolutionResult.rootVariant }
}
// After
abstract class NewTask : DefaultTask() {
@get:Input
abstract val resolutionResult: Property<ResolutionResult>
@TaskAction
fun traverse() {
// Access to convenience APIs on ResolutionResult.
resolutionResult.get().allDependencies
resolutionResult.get().allComponents
}
}
tasks.register<NewTask>("after") {
resolutionResult = configurations.runtimeClasspath.map { it.incoming.resolutionResult }
}
Source dependencies now work with the Configuration Cache.
Source dependencies let a build resolve a dependency by building it directly from a version control repository. They are declared with a sourceControl { vcsMappings { ... } } block in the settings file.
A build that maps a dependency to a Git repository now stores and reuses a Configuration Cache entry like any other build:
// settings.gradle.kts
sourceControl {
vcsMappings {
withModule("org.example:lib") {
from(GitVersionControlSpec::class) {
url = uri("https://github.com/example/lib.git")
}
}
}
}
When a source dependency is selected with a dynamic selector — a branch, latest.integration, or a version range — the resolved commit can move between builds, so Gradle always invalidates the Configuration Cache entry for that build and re-resolves it, mirroring how dynamic versions of external dependencies are handled. A source dependency pinned to a static version reuses the cached entry until an input, such as the vcsMappings configuration or the resolved upstream commit, changes.
See the Configuration Cache chapter in the Gradle User Manual for more details.
Gradle provides Isolated Projects, an incubating feature that enables parallel project configuration.
Isolated Projects has graduated from experimental to incubating. It can now be enabled with the stable org.gradle.isolated-projects property and the new --isolated-projects CLI option, dropping the .unsafe. segment from the previous names.
# gradle.properties
org.gradle.isolated-projects=true
The legacy org.gradle.unsafe.isolated-projects property names are now deprecated and will be removed in a future release. They continue to work as aliases for now. See the upgrade guide for the full list of renamed properties.
Builds adopting Isolated Projects typically contain constraint violations that must be fixed over time. Isolated Projects now offers three modes, each suited to a different stage of that journey:
org.gradle.isolated-projects.diagnostics) — project configuration runs sequentially and the build continues past violations, reporting all of them deterministically. Use this to discover what needs fixing during migration.org.gradle.isolated-projects.dangerously-ignore-problems) — violations are reported but do not fail the build, and parallel configuration stays active. Use this to estimate the parallel build or IDE sync speedup before fixing every violation. Build outputs may be incorrect while violations are ignored, so never use this mode to produce artifacts.The opt-in modes can also be combined, for example to complete an IDE sync that concurrency errors would otherwise interrupt:
# gradle.properties
org.gradle.isolated-projects=true
org.gradle.isolated-projects.diagnostics=true
org.gradle.isolated-projects.dangerously-ignore-problems=true
In all modes, the severity of Isolated Projects violations is now independent of the Configuration Cache --configuration-cache-problems=warn flag.
Gradle provides an intuitive command-line interface, detailed logs, and a structured problems report that helps developers quickly identify and resolve build issues.
Gradle's test logging now surfaces test-framework startup failures from TestNG, JUnit 4, and JUnit Platform even when the default granularity would otherwise hide them.
Previously, when these frameworks failed to initialize (for example, when a TestNG test class threw an exception from its constructor, a JUnit 4 suite could not be started, or a Jupiter @BeforeAll lifecycle hook aborted a container) the failure was silently filtered out by the default granularity. Users would see only > There were failing tests and had to read the XML report to find the underlying cause:
> Task :test FAILED
> There were failing tests. See the report at: file:///.../build/reports/tests/test/index.html
FAILURE: Build failed with an exception.
These framework-startup failures now bypass the granularity filter and are always written to the console by default:
> Task :test
ExampleTest > initializationError FAILED
framework-startup org.testng.TestNGException: Cannot instantiate class ExampleTest
at org.testng.internal.ObjectFactoryImpl.newInstance(...)
...
The testLogging.events predicate still applies; explicitly silencing FAILED events is honored.
The new TestFailureDetails.isFrameworkFailure() predicate exposes this distinction to Tooling-API and Build-Scan consumers, who may render framework-startup failures differently from ordinary test failures.
See the Test logging section in the Gradle User Manual for more details.
threadPoolFactoryClass works with TestNG 7.10 and laterTestNG 7.10 replaced its thread-pool factory setter (setExecutorFactoryClass(String)) with a new API (setExecutorServiceFactory(IExecutorServiceFactory)). This release adds support for thread pool factories that implement this API on supported TestNG versions.
Gradle now detects which API the runtime TestNG version exposes and handles it accordingly:
org.testng.IExecutorServiceFactory.org.testng.thread.IExecutorFactory.The test task configuration remains unchanged; only the interface the user-supplied class must implement differs across TestNG versions:
tasks.named<Test>("test") {
useTestNG {
threadPoolFactoryClass = "com.example.MyExecutorServiceFactory"
}
}
Gradle provides an intuitive command-line interface, detailed logs, and a structured problems report that helps developers quickly identify and resolve build issues.
To keep stack trace capture affordable, Gradle attached a source location to only the first 50 problems per build, so builds that report many problems (especially deprecations) left most of them without a file and line number.
Gradle now captures a source location for up to 2000 additional problems past that cap. The capture mechanism is far cheaper than a full stack trace, so the added coverage has negligible cost. As a result, the console, the problems report, and the Tooling API show a source location for many more problems than before.

Run with --warning-mode=all to remove the limit and capture a source location for every problem. Past the cap, including under --warning-mode=all and fail, the capture keeps the originating build logic down to the calling script, enough to locate the problem, rather than the full call chain.
See the CLI reference in the Gradle User Manual for more details.
Gradle provides rich APIs for build engineers and plugin authors, enabling the creation of custom, reusable build logic and better maintainability.
Gradle produces reproducible archives by default, using fixed timestamps for all entries. However, some environments, such as those following the SOURCE_DATE_EPOCH specification, require a meaningful, verifiable timestamp rather than a fixed default.
Archive tasks now support a reproducibleFileTimestamp property that lets you set a custom timestamp for every entry in the archive:
import java.time.Instant
tasks.withType<AbstractArchiveTask>().configureEach {
reproducibleFileTimestamp = providers.environmentVariable("SOURCE_DATE_EPOCH").map {
Instant.ofEpochSecond(it.toLong()).toEpochMilli()
}
}
See the Timestamp for files inside archives section in the Gradle User Manual for more details.
ResolvedArtifactResultThe ResolvedArtifactResult.getAttributes() and ResolvedArtifactResult.getCapabilities() methods have been introduced to provide access to the attributes and capabilities of a resolved artifact without going through the ResolvedArtifactResult.getVariant() method. ResolvedArtifactResult.getVariant() is still available, but will be deprecated in a future release.
getInputStream() method on BuildCacheEntryWriterAuthors of custom BuildCacheService implementations can now obtain cache entry content as an InputStream via BuildCacheEntryWriter.getInputStream(), as an alternative to writing to an OutputStream via writeTo. Consuming an InputStream can be more efficient for I/O, especially for asynchronous HTTP clients.
Gradle provides comprehensive support for Native development and JVM languages, featuring automated Toolchains for seamless JDK management.
DomainObjectCollection.getElements() returns a Provider<? extends Collection<T>> and acts as an important bridge between the Domain Object Collection and Provider APIs. This API is similar to the existing FileCollection.getElements() method.
The returned provider carries build dependencies, meaning dependencies carried by providers added via addLater and addAllLater are reflected in the returned elements provider:
val container = objects.domainObjectSet(MyType::class.java)
container.addLater(someProvider)
// Lazily access all elements as a Provider
val allElements: Provider<out Collection<MyType>> = container.elements
tasks.register("process") {
inputs.property("items", allElements)
doLast {
println(allElements.get())
}
}
See the Collections section in the Gradle User Manual for more details.
The Groovydoc task now supports Java toolchains, aligning it with GroovyCompile, Javadoc, and ScalaDoc. By default the task uses the project's configured toolchain, and it can also be configured per-task:
tasks.named<Groovydoc>("groovydoc") {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}
}
As Groovydoc now runs in a separate worker process, a new incubating maxMemory property is available to control the heap size of that process for larger code bases:
tasks.named<Groovydoc>("groovydoc") {
maxMemory = "1g"
}
Gradle provides robust security features and underlying infrastructure to ensure that builds are secure, reproducible, and easy to maintain.
Gradle's dependency verification helps you mitigate security risks by ensuring downloaded artifacts match expected checksums or are signed with trusted keys.
Dependency verification metadata already lets you annotate checksums with origin and reason attributes to document where a checksum was obtained and why it is trusted.
Starting with this release, the same origin and reason attributes are also supported on the <trusted-key> and <pgp> elements, so you can record where a public key was verified (for example, the URL it was downloaded from) directly alongside the key:
<trusted-keys>
<trusted-key id="8756c4f765c9ac3cb6b85d62379ce192d401ab61"
group="com.github.javaparser"
origin="https://keyserver.ubuntu.com"
reason="Verified against the maintainer's website"/>
</trusted-keys>
These attributes are purely informational: Gradle preserves them across read/write cycles but never uses them during verification. Existing verification metadata files continue to be read without changes; files written by this version of Gradle use the new dependency-verification-1.4.xsd schema.
When dependency verification fails because an artifact was signed with a key that could not be found on any key server, it can be hard to tell whether you are pulling a brand-new dependency for the first time or whether a previously trusted dependency has had its signing key rotated.
Gradle now appends the number of other keys you already trust for the failing artifact to the message, distinguishing keys trusted for the specific group:module from keys trusted for the whole group:
> On artifact foo-1.0.jar (org:foo:1.0) in repository 'maven': Artifact was signed with key '14F53F0824875D73' but it wasn't found in any key server so it couldn't be verified (1 other key is already trusted for module 'org:foo'; 3 other keys are already trusted for group 'org')
A non-zero count is a strong signal that the signing key has been rotated rather than that you are trusting the module or group for the first time, making it easier to react appropriately. This note now appears in both the console output and the generated HTML verification report.
See the Verifying dependency signatures section in the Gradle User Manual for more details.
Gradle provides various incremental updates and performance optimizations to ensure the continued reliability of the build ecosystem.
Generating the type-safe Kotlin DSL accessors for a project produces Kotlin source files. For some projects those files can be sizeable, but their generation is fast.
Storing and fetching those files adds its own overhead when the Build Cache is in use. That overhead alone is comparable to or higher than the cost of just regenerating the accessors. Gradle therefore no longer stores Kotlin DSL accessor generation in the Build Cache by default.
Builds that use a remote Build Cache will regenerate accessors locally instead of downloading them; in-build deduplication of accessor generation is unaffected. Kotlin DSL script compilation continues to be cached as before.
See the Type-safe model accessors section in the Gradle User Manual for more details.
File system watching lets Gradle skip work between builds by tracking file changes from the operating system. The project cache directory, by default .gradle/ in the root project, stores per-project state that Gradle reuses across builds.
Some teams move this state out of the project tree using --project-cache-dir or org.gradle.projectcachedir, for example to keep build state on a separate or faster file system, or to share a project across users on CI. Until now, doing so was incompatible with file system watching, so these users could not benefit from it.
Gradle has decoupled the two. File system watching works with any project cache directory location, including one on a file system that does not support watching:
$ ./gradlew build --watch-fs --project-cache-dir /custom/path
See the Excluding files and directories section in the Gradle User Manual for more details.
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.
project() in DependencyHandlerproject(String) in DependencyHandlercreateProjectDependency() in DependencyFactorycreateProjectDependency(String) in DependencyFactoryThe Best Practices chapter grew significantly with guidance covering task authoring, lazy properties, dependency configuration, and CI hygiene:
Project instance from a task action for Configuration Cache compliance.map and flatMap to preserve task dependencies through Provider chains.@Incubating APIs carefully before adopting them in production build logic../gradlew on untrusted projects.The Configuration Cache chapter received a substantial pass this release, including a reorganised main page, expanded coverage of warn mode, refined guidance on BuildServiceParameters, a clearer explanation of how dependency resolution types interact with the cache, and improved Javadoc on the Configuration Cache classes themselves. The Build Services page was also updated to reflect Isolated Projects compatibility.
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 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.