Upgrading Gradle

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Gradle 9.8.1 (released on 7 October 2026) and the upgrade guides of the user manual as of 16 October 2026. Gradle 8.14.6 (released on 8 October 2026) is the last 8.x release and is the starting point of the main upgrade described here. Every example marked as run was executed with the 9.8.1 wrapper on macOS (Apple silicon), in scratch copies of the tidepool build, once with *.gradle.kts files and once with *.gradle files wherever the syntax differs. The Gradle daemon ran on Oracle JDK 26 (java version "26"), the default JDK of the machine, except where the text says that JetBrains Runtime 17.0.14 was used. A gradle.properties file in each scratch build set org.gradle.jvmargs=-Xmx512m -Duser.language=en -Duser.country=US, so that the messages are in English, and org.gradle.java.installations.auto-download=false, so that no JDK could be downloaded. The scratch directory is shown as <work>; the at ... stack frame and (Run with --stacktrace ...) lines that follow each deprecation warning, the lines announcing the start of a daemon, and the long Welcome to Gradle text of the help task are left out of the listings. The examples are compatible with the configuration cache and ran with --configuration-cache except where the text says otherwise: a configuration cache hit does not repeat the warnings that are printed while the build is configured, so every run that looks for deprecations ran with --no-configuration-cache.

Gradle 8.14.x was not available here, and no Gradle distribution was downloaded. Everything said about 8.14, and every difference between 8.14 and 9.x that was not observable on 9.8.1, comes from the official upgrade guides and release notes and is labelled "not run here". Three more things were not run: --scan (publishing a Build Scan to a Develocity service requires accepting its terms of use), running Gradle 9 on a Java older than 17 (no such Java is installed) and any wrapper run that would download a distribution from services.gradle.org. The wrapper properties shown on this page are the official ones, with the services.gradle.org URL; the scratch builds pointed the same wrapper at a local copy of the 9.8.1 archive.

An upgrade of a Gradle build is not one step. A Gradle release changes three things at once: the engine that runs your build (the JVM it needs, the Kotlin and Groovy that compile your scripts, the defaults of the bundled plugins), the API that your scripts and plugins call (deprecated in one release and removed in a later one), and the behavior of tasks that you did not change. The user manual answers with one upgrade guide per major line and a fixed procedure, and this page walks through the procedure, then through each guide that matters for a build that is on 8.14 today, then through the older majors, because the build files that Gradle users still find in books, blog posts and old repositories (including the examples of Gradle in Action, which targets Gradle 1.7) stop working on 9.8.1 in specific, recognizable ways. The release history behind all of this is on Gradle Versions, Compatibility & Release History; this page is where each removal is explained and fixed. The wrapper task itself is described on Gradle Wrapper, the option --warning-mode on Gradle Command Line, the configuration cache on Configuration Cache & Isolated Projects, and the other two tools' equivalents are in the last section: Maven’s are on Migrating to Maven 4 and Maven Wrapper, Ant’s on Ant: Deprecations & Migration.

How Gradle versions and deprecations work

Since Gradle 9.0.0 the version number follows semantic versioning: MAJOR.MINOR.PATCH, where a major release may remove public APIs, a minor release adds features and deprecates others, and a patch release fixes bugs and vulnerabilities (9.8.1 is a patch release of 9.8.0 that closes three high-severity vulnerabilities in the communication between client, daemon and workers, and in the handling of repositories that fail to establish an SSL connection). Before 9.0 the versions were MAJOR.MINOR (8.14) with the patch number appearing only when needed (8.14.6), and a "minor" release such as 7.3 or 8.2 could already contain large features.

The feature lifecycle turns the numbering into a promise. A documented feature is public, and is not removed or changed incompatibly without first being deprecated; a deprecated feature keeps working, prints a warning when it is used, and "may be removed in the next major release"; the manual adds that deprecated features may be supported across major releases but that this is not guaranteed. The deprecation messages of Gradle 9.8.1 state a target release in each message: "This will fail with an error in Gradle 10" for most, "scheduled to be removed in Gradle 11" for a few that were deprecated recently. The incubating state is the exception to the promise: an incubating API may change in any release and the changes are listed in the release notes. The full lifecycle is explained on the versions page.

The consequence for upgrading is simple. A build that is free of deprecation warnings on the last minor release of a line (8.14.x) will, with very few exceptions, compile and run on the first release of the next line (9.0.0), and a build that is free of warnings on a 9.x release will run on every later 9.x release. Almost all upgrade pain comes from skipping that discipline: from moving more than one major release at once, from ignoring the warning summary, or from plugins that use internal APIs.

The upgrade guides of the user manual have a naming scheme that is easy to misread. Each file upgrading_version_N.html covers the minor releases of line N and the move from the last N.x to the first release of the next line; the move to Gradle 9.0.0 has a page of its own. The table lists them with the sections that this page uses.

Guide Covers Used on this page for

upgrading_version_4.html

4.1 to 4.10, and 4.10 to 5.0

<< removed, the Kotlin DSL 1.0 and BOM import release

upgrading_version_5.html

5.2 to 5.6, and 5.6 to 6.0

FindBugs, JDepend, OSGi, announce and Compare Gradle Builds plugins removed

upgrading_version_6.html

6.3 to 6.9, and 6.9 to 7.0

compile, runtime, the maven plugin and uploadArchives removed; jcenter() deprecated; the JavaScript plugins removed

upgrading_version_7.html

7.x, and 7.6 to 8.0

warnings that became errors in 8.0

upgrading_version_8.html

8.1 to 8.14

everything deprecated in 8.x and removed in 9.0

upgrading_major_version_9.html

8.14 to 9.0.0

the breaking changes of the major release

upgrading_version_9.html

9.0.0 to the latest 9.x (9.8.1)

the minor releases and the deprecations announced for Gradle 10

The upgrade procedure

The user manual gives the same four steps at the top of every upgrade guide, from the 4.x guide to the 9.x one, and they are the whole method. In the guide for moving to Gradle 9.0.0 they read:

  1. Run gradle help --scan and look at the Deprecations view of the Build Scan; alternatively run gradle help --warning-mode=all to see the deprecations in the console, "though it may not report as much detailed information".

  2. Update your plugins, "because a major release can remove public APIs" and the newest version of a plugin is the most likely to be compatible.

  3. Run gradle :wrapper --gradle-version 9.0.0 (or the version you are moving to).

  4. Run the project and debug any errors with the troubleshooting guide.

The guide for moving to 9.0.0 adds a precondition before the first step (for migrating within Gradle 8.x, read the older migration guide first), and the guide for the 9.x releases says the same about the 9.0.0 guide. The 4.x guide adds two more pieces of advice that remain good: move to the latest release of your current line before moving to the next major one, because "Gradle 5.x will generate (potentially less obvious) errors if you try to upgrade directly to it", and do not upgrade Gradle and migrate to the Kotlin DSL at the same time, so that a failure has one cause. The flow, with the commands, is:

flowchart TD start["Build is green on the current release"] --> latest["Move to the latest patch of the current minor, then to the last minor of the line (8.14.x)"] latest --> warn["help --warning-mode=all or help --scan: list deprecations"] warn --> fix["Fix every deprecation, update plugins to current releases"] fix --> clean{"Build free of deprecations and green?"} clean -->|"no"| warn clean -->|"yes"| wrap["wrapper --gradle-version N (twice)"] wrap --> run["Run the build, read errors with the troubleshooting guide"] run --> done{"Green?"} done -->|"no"| fix2["Fix, or use the guide for the target release"] fix2 --> run done -->|"yes"| ci["Commit the wrapper files, set --warning-mode=fail on CI"]

Step 1: find the deprecations

By default Gradle prints the deprecation warnings of a build only as a one-sentence summary at the end of the build. That is useful as a signal, and useless as a list. This is the summary on 9.8.1 for a build that uses a deprecated Groovy DSL idiom (group 'io.tidepool', the "space assignment" syntax) and Project.getProperties(); the build succeeds:

$ ./gradlew :child:help --warning-mode=summary --no-configuration-cache --console=plain
> Configure project :child
hello

> Task :child:help

Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/9.8.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

--warning-mode selects what is printed: summary (the default, shown above), all (every warning, with the build file and line), fail (every warning, and the build fails if there was any) and none. The same choice can be made persistent with the Gradle property org.gradle.warning.mode, in gradle.properties or with -D on the command line, and the command line wins over the file. With fail the same build prints each warning, and ends with a failure that names the first offending line of the build file:

$ ./gradlew :child:help --warning-mode=fail --no-configuration-cache --console=plain
> Configure project :child
Build file '<work>/child/build.gradle': line 2
Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated. This is scheduled to be removed in Gradle 10. Use assignment ('group = <value>') instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax
Build file '<work>/child/build.gradle': line 3
The Project.getProperties method has been deprecated. This will fail with an error in Gradle 10. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecated_get_properties
hello

> Task :child:help

[Incubating] Problems report is available at: file://<work>/build/reports/problems/problems-report.html

FAILURE: Build failed with an exception.

* Where:
Build file '<work>/child/build.gradle' line: 2

* What went wrong:
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

BUILD FAILED in 1s
1 actionable task: 1 executed
[exit 1]

Each warning has the same anatomy, which is worth knowing because the target of the link is the upgrade guide. First the sentence of what was used; then when it stops working ("This will fail with an error in Gradle 10", or "scheduled to be removed in Gradle 11"); then the replacement; and last the link to the section of the guide that explains the change, upgrading_version_9.html#deprecated_get_properties in the second warning above, followed by the place in the build ("Build file …​: line 3", or the at line that was left out of the listing). In a Kotlin DSL build the Kotlin compiler adds its own w: lines for the deprecated declarations before Gradle’s message, as the by registering delegates below show. The at line says which script or class made the call, and --stacktrace prints the whole stack of a warning, which is how a deprecation that is triggered by a plugin is traced to the plugin.

The run also wrote a report, and the output announced it (the line starting with [Incubating] Problems report, which the other listings of this page leave out): build/reports/problems/problems-report.html lists the deprecations and other problems of the build as an HTML page that can be opened without a Build Scan. The report is incubating in 9.8.1.

The second way is the Build Scan: gradle help --scan collects the same deprecations in a Deprecations view that groups them by the plugin or script that caused them, with the stack trace and the link to the upgrade guide, which is why the guides recommend it first. Publishing a scan sends build data to a Develocity service and requires accepting its terms of use, so --scan was not run here; the scan options are described on Performance Profiling & Build Scans.

Three rules make the output reliable. Run with --no-configuration-cache, or after changing a file of the build, because a configuration cache hit skips the configuration phase where most of the warnings are produced. Run the tasks that your CI runs, not only help, because a deprecation that is triggered by a task action (reading Task.project, for example) is printed only when the task executes. Run the build of every included build and of buildSrc or build-logic too: they are separate builds and their warnings are reported with the others.

Step 2: update the plugins

A plugin is code that runs inside Gradle, so it breaks when the Gradle API it was compiled against changes. The 9.0.0 guide states the practical rule ("using the latest version of a plugin increases the likelihood that it is already compatible with the new major Gradle version") and lists, for plugins that Gradle knows about, floors below which a plugin is not supported by Gradle 9:

Plugin Lowest supported on Gradle 8.x Lowest supported on Gradle 9.x

Kotlin Gradle Plugin

1.6.10

2.0.0

Android Gradle Plugin

7.3.0

8.4.0 (and AGP 9.x from Gradle 9.6, see below)

Gradle Enterprise plugin (now the Develocity plugin, com.gradle.develocity)

3.0

3.13.1; versions before 4.0 are deprecated from 9.6

Plugins written with the Kotlin DSL and built with Gradle 9

(built with 8.x: usable down to Gradle 6.8)

usable only on Gradle 8.11 or newer (the first release that embeds Kotlin 2)

Plugins written with the Groovy DSL and built with Gradle 9

(built with 7.x or 8.x: usable down to Gradle 5.0)

usable only on Gradle 7.0 or newer (the first with Groovy 3)

Two of these rows matter beyond the table. An Android project has to move AGP before it moves Gradle: AGP 8.4.0 is the floor for Gradle 9.0.0, and Gradle 9.6 removed internal classes of the Problems API that AGP 8.x used, so 9.6 and later need AGP 9.x (the failure mentions the removed type; staying on Gradle 9.5 is the other fix). A plugin author who needs to support Gradle older than 8.11 must build the plugin with Gradle 8.x, because the Kotlin 2.2 compiler of Gradle 9 rejects language versions below 1.8 and always writes Kotlin metadata version 2; the guide shows the languageVersion and apiVersion setting for an 8.x build and says it fails on Gradle 9. How to find the plugins a build uses and their versions: ./gradlew buildEnvironment prints the build script classpath of each build, and the plugin versions of the version catalog are in Version Catalogs. Third-party plugins that fail with a NoSuchMethodError, NoClassDefFoundError or "Could not find method" during configuration are the typical symptom of a plugin that uses an API Gradle removed; the fix is the plugin’s own newest release, and if there is none, replacing the plugin.

Step 3: update the wrapper

The wrapper is how the version of Gradle becomes part of the repository (see Gradle Wrapper for every key of gradle-wrapper.properties), so upgrading is running the wrapper task with the new version and committing the result. The manual recommends the task over editing distributionUrl, because the task also refreshes the scripts and the JAR. The guides give the command as gradle :wrapper --gradle-version 9.0.0, and the wrapper chapter gives the pair that is needed to update the JAR as well:

./gradlew wrapper --gradle-version 9.8.1 --gradle-distribution-sha256-sum <sha-256 of the -bin.zip> && ./gradlew wrapper

The first call runs with the old Gradle and rewrites gradle-wrapper.properties; the second runs with the new Gradle, which downloads it and writes the new gradle-wrapper.jar and scripts. When the properties file contains a distributionSha256Sum (a project that verifies its distribution has one) the task refuses to leave a stale checksum behind, so the new sum has to be passed with --gradle-distribution-sha256-sum; the checksums of all releases are on gradle.org/release-checksums. Moving from 8.14.6 to 9.0.0 is the same command with 9.0.0, and it was not run here, because it would download the distribution of 9.0.0 on the second call.

What could be run is the first half of the sequence with the installed 9.8.1: a scratch project whose properties file was written by hand to look like an older one (a 9.7.1 URL and its checksum) was upgraded with the installed gradle executable, so that nothing was downloaded, and with --no-validate-url, so that the task did not contact the server:

$ gradle wrapper --gradle-version 9.8.1 --gradle-distribution-sha256-sum dce76f55f8e251a3a1f130eb120f30b3d271de2b76c9b0729d316b5a1b6dc01f --no-validate-url
> Task :wrapper

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.8.1/userguide/configuration_cache_enabling.html

The difference between the properties file before and after (diff, with < for the old file):

3,4c3,4
< distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
< distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
---
> distributionSha256Sum=dce76f55f8e251a3a1f130eb120f30b3d271de2b76c9b0729d316b5a1b6dc01f
> distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.1-bin.zip
6c6,8
< validateDistributionUrl=true
---
> retries=0
> retryBackOffMs=500
> validateDistributionUrl=false

The URL and the sum changed together, and the file gained retries and retryBackOffMs (the download retry settings, new in Gradle 9.5) and validateDistributionUrl=false from the --no-validate-url option; a normal run would have kept true, which makes the task check with a request that the URL exists. The scripts gradlew and gradlew.bat and the JAR were identical, because the old files in this scratch project were already those of 9.8.1; moving from a real 8.14 wrapper would change them, which is why the second call is part of the procedure. After the second call ./gradlew --version shows the new release and the four files (gradlew, gradlew.bat, gradle/wrapper/gradle-wrapper.jar, gradle/wrapper/gradle-wrapper.properties) are committed together.

Two details of the release history affect the move. The Windows start script was reworked in 9.5.0 (it no longer leaks environment variable changes into the invoked program, and a custom start script template or a batch file that calls gradlew.bat and relies on the "Terminate batch job" prompt must be reviewed); the 9.5 guide warns that upgrading the wrapper to the new script from a Gradle older than 8.14 can fail once, because cmd.exe re-reads a script that has changed under it, and recommends going through 8.14 or 9.0.0 first. And since 9.4 the priority of system properties for a wrapper run follows the documented order (command line, then the Gradle user home gradle.properties, then the project’s gradle.properties); before it, a property in the project file could override one given on the command line.

The JVM that runs the wrapper and the daemon is the other half of "which Gradle". Gradle 9.8.1 on JetBrains Runtime 17.0.14, the oldest Java that Gradle 9 supports for the daemon, with the same wrapper and no other change:

$ JAVA_HOME=<jbr-17.0.14>/Contents/Home ./gradlew --version

Build time:    2026-10-07 16:57:46 UTC
Revision:      39daf39feb60a18b1b6ddccde53366522fef516f

Kotlin:        2.4.10
Groovy:        4.0.33
Ant:           Apache Ant(TM) version 1.10.17 compiled on April 6 2026
Launcher JVM:  17.0.14 (JetBrains s.r.o. 17.0.14+1-b1367.22)
Daemon JVM:    ~/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home (no Daemon JVM specified, using current Java home)
OS:            Mac OS X 27.0.1 aarch64

Step 4: run, then make CI keep the build clean

After the wrapper has been updated the build is run and the errors are read with the troubleshooting guide, which has sections for installation, dependency resolution, performance, attaching a debugger, tasks that rerun unexpectedly and IDE integration (see Best Practices & Troubleshooting). When a plugin turns out to be incompatible, and no newer release exists, staying on the last working Gradle for a while is a legitimate decision; going back is the same wrapper task with the old version.

Once the build is green, keep it green: a build that fails on a new deprecation is a build that is never surprised by a major release. In gradle.properties, for CI only through a CI-specific file or a -D argument, so that a developer who updates a plugin locally is not blocked by a warning that comes from it:

org.gradle.warning.mode=fail

The property worked as the command-line option did: a build with the property set failed on the deprecated group 'io.tidepool' with the same message as above, and -Dorg.gradle.warning.mode=none on the command line overrode it and the build succeeded.

$ ./gradlew :child:help --console=plain
> Configure project :child
Build file '<work>/child/build.gradle': line 2
Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated. This is scheduled to be removed in Gradle 10. Use assignment ('group = <value>') instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax

> Task :child:help

FAILURE: Build failed with an exception.

* Where:
Build file '<work>/child/build.gradle' line: 2

* What went wrong:
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.

BUILD FAILED in 412ms
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.8.1/userguide/configuration_cache_enabling.html
[exit 1]

From Gradle 8.14 to 9.0.0

Gradle 9.0.0 (31 July 2025) is the breaking release of the current line: it is the first to require a newer Java to run, the first to embed Kotlin 2 and Groovy 4 for the build scripts, and the release that removed what had been deprecated in 7.x and 8.x. Everything in this section is in the guide Upgrading from 8.x to 9.0.0. The headline items, then the details with the runs:

Change What it means for a build

Java 17 or newer to run the daemon

Gradle 8.14 runs on Java 8 to 24; Gradle 9 needs 17 or newer for the daemon. The code can still target older Java through toolchains.

Kotlin 2.2 and Groovy 4

Build scripts and plugins are compiled by a different compiler and language version.

Plugin floors

Kotlin Gradle Plugin 2.0.0, Android Gradle Plugin 8.4.0, Develocity plugin 3.13.1.

Removed APIs

jcenter(), the conventions (sourceCompatibility, archivesBaseName and others as project properties), Project.exec and Project.javaexec, the "name"() task reference in the Kotlin DSL, -b and -c on the command line, and more.

Behavior changes without an error

Reproducible archives by default, tasks that silently stop running tests, a test task that fails when it finds no tests, assemble no longer building artifacts of arbitrary configurations.

Configuration cache

The "preferred" mode: still off by default, but a task that is incompatible always discards the cache entry, and unsupported build event listeners fail the build.

Java 17 to run Gradle

"Gradle 9.0.0 requires a Java Virtual Machine (JVM) version 17 or higher to run the Gradle Daemon." The change was announced in the 8.10 guide, and it is narrow. Gradle can still compile for older Java (the guide says it keeps supporting compilation to Java 6 or later, and the Worker API, running tests on Java 8 and the Tooling API and TestKit clients remain compatible with Java 8); the wrapper and the command-line launcher can start on Java 8, but they then need a Java 17 or newer for the daemon, set with the Daemon JVM criteria, with org.gradle.java.home, or with JAVA_HOME. --no-daemon and ProjectBuilder in unit tests need Java 17 as well, since they run Gradle in the test JVM. This is why a CI image on Java 11 must change while the project can keep targeting Java 8 or 11:

  • Put the Java that the build targets in the build, with a toolchain (Gradle Toolchains): java { toolchain { languageVersion = JavaLanguageVersion.of(11) } }.

  • Put the Java that runs Gradle in the repository, with the Daemon JVM criteria (gradle/gradle-daemon-jvm.properties, created by ./gradlew updateDaemonJvm, described on The Gradle Daemon); the 9.2 release made that feature stable.

That Gradle 9.8.1 starts on JDK 17 was shown in step 3. Starting it on an older Java was not run here, as no such Java is installed. Java 27 became a supported daemon and toolchain Java in 9.8.0.

Kotlin 2.2 and Groovy 4

Gradle 9.0.0 upgraded the embedded Kotlin from 2.0.21 to 2.2.0 and Groovy from 3.0.24 to 4.0.27; in 9.8.1 they are Kotlin 2.4.10 and Groovy 4.0.33, as ./gradlew --version prints:

Build time:    2026-10-07 16:57:46 UTC
Revision:      39daf39feb60a18b1b6ddccde53366522fef516f

Kotlin:        2.4.10
Groovy:        4.0.33
Ant:           Apache Ant(TM) version 1.10.17 compiled on April 6 2026
Launcher JVM:  26 (Oracle Corporation 26+35-2893)
Daemon JVM:    /Library/Java/JavaVirtualMachines/jdk-26.jdk/Contents/Home (no Daemon JVM specified, using current Java home)
OS:            Mac OS X 27.0.1 aarch64

The embedded versions through the 9.x line (each from the upgrade guide of the release; a patch release can bump Kotlin or Groovy again, so the column is the version at the minor release):

Gradle Kotlin Groovy

8.14

2.0.21 (language version 1.8 for scripts and plugins)

3.0.24

9.0.0

2.2.0 (language version 2.2 across the toolchain)

4.0.27

9.1.0

2.2.0

4.0.28

9.2.0

2.2.20

4.0.28

9.3.0

2.2.21

4.0.29

9.4.0

2.3.0

4.0.29

9.5.0

2.3.20

4.0.29

9.6.0

2.3.21

4.0.32

9.7.0

2.4.0

4.0.32

9.8.0 and 9.8.1

2.4.10

4.0.33

Kotlin. The Kotlin DSL was compiled with language version 1.8 for compatibility, even though Gradle 8.11 and later embedded Kotlin 2.0. From 9.0 scripts, precompiled script plugins and plugins written in Kotlin use language version 2.2, so the breaking changes of Kotlin 2.0 and 2.1 apply, and the support for language versions 1.4 to 1.7 was removed for plugin authors. Kotlin 2.4.0 (Gradle 9.7) removed the K1 compiler altogether; the smallest compile target is 2.0, which is itself deprecated. Two source-level changes are the ones that builds hit. The script label this@Build_gradle (and this@Settings_gradle, this@Init_gradle) no longer exists; use project, settings or gradle:

* Where:
Build file '<work>/child/build.gradle.kts' line: 1

* What went wrong:
Script compilation error:

  Line 1: println(this@Build_gradle)
                      ^ Unresolved label.

1 error

The second is nullability. The Gradle API is annotated with JSpecify instead of the dormant JSR-305 from 9.0.0, and Kotlin 2.1 and later read those annotations strictly, so code that compiled before can fail: a Provider<T> has a bound T : Any, so a Kotlin extension function must say so, and Property<String?> is no longer valid. This is the real failure, in a buildSrc Kotlin file of a scratch build, followed by the fix:

buildSrc/src/main/kotlin/Helpers.kt (fails)
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider

fun <T> Provider<T>.describe(): String = "provider of " + orNull

fun nullableProperty(p: Property<String?>): String? = p.orNull
> Task :buildSrc:compileKotlin FAILED
e: file://<work>/buildSrc/src/main/kotlin/Helpers.kt:4:18 Type argument is not within its bounds: type parameter 'T#2 (of interface Provider<T : Any>)' must be subtype of 'Any', but actual: 'T#1 (of f ...
e: file://<work>/buildSrc/src/main/kotlin/Helpers.kt:6:34 Type argument is not within its bounds: type parameter 'T (of interface Property<T : Any>)' must be subtype of 'Any', but actual: 'String?'.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':buildSrc:compileKotlin' (registered by plugin class 'org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper').
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.btapi.BuildToolsApiCompilationWork
   > Compilation error. See log for more details
buildSrc/src/main/kotlin/Helpers.kt (fixed)
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider

fun <T : Any> Provider<T>.describe(): String = "provider of " + orNull

fun nullableProperty(p: Property<String>): String? = p.orNull

Plugins that still use the javax.annotation annotations continue to work. A related rule for plugin authors: kotlinDslPluginOptions.jvmTarget was removed, and the target of the compiled Kotlin follows the Java toolchain.

Groovy. Groovy 4 renamed packages (to satisfy the Java module system’s split-package rule) and rewrote the parser, so Groovy build scripts that use classes such as groovy.util.* directly may need new imports. Four behavior changes need attention. First, a getter with an is prefix and a Boolean (not boolean) return type is no longer a property for Groovy 4; Gradle still treated such getters as properties of its own types, announced the change as a deprecation in 8.13, and will drop it in Gradle 10. In a build script of your own:

* Where:
Build file '<work>/child/build.gradle' line: 2

* What went wrong:
A problem occurred evaluating project ':child'.
> No such property: enabled for class: Flag

The fix is a get method (or boolean as the type): the 8.13 guide shows keeping the old isX method with @Deprecated and @ReplacedBy and adding getX. Second, closures with the DELEGATE_FIRST strategy now look up dynamic properties and methods on the delegate before the owner, which can change what a bare name means inside with { } blocks (qualify with owner. or this., or use @CompileStatic). Third, closures defined in a parent class may no longer reach its private properties or methods from a subclass (a Groovy bug that stays until Groovy 5; the workaround is @CompileStatic). Fourth, plugins written in Groovy 3 that call super methods may fail at run time on Gradle 9, because Groovy 4 resolves super calls differently; rebuild such plugins with Gradle 9.

Removed APIs and features

Gradle 9.0.0 removes what was deprecated during 7.x and 8.x. The table lists the removals that builds meet most often; the guide lists more (see the last row). The "deprecated" column is the release whose upgrade guide first announced it.

Removed in 9.0.0 Deprecated in Use instead

jcenter()

7.0

mavenCentral(). JCenter was redirected to Maven Central in August 2024 and Bintray shut down in 2021.

The conventions: Project.getConvention(), Task.getConvention(), org.gradle.api.plugins.Convention, and with them the project properties added by plugins (sourceCompatibility, targetCompatibility, archivesBaseName, libsDirName, distDirName, war, ear, projectReports)

8.2

The extensions: java { sourceCompatibility = ... } (or a toolchain), base { archivesName = ... }, and configuring the war, ear and report tasks directly.

-b/--build-file, -c/--settings-file and GradleBuild.buildFile

7.1 (8.1 added the StartParameter and GradleBuild properties)

Run Gradle from the directory of the build, or select it with -p/--project-dir.

Project.exec, Project.javaexec and the script-level exec and javaexec (Kotlin DSL scripts, settings, init scripts)

8.11

Inject ExecOperations into a task (a drop-in replacement at execution time), or providers.exec and providers.javaexec or a ValueSource at configuration time.

The "name"() form to reference a task or domain object in the Kotlin DSL

8.7

named("name"), register, getByName or the typed accessor.

Libraries and bundles of a version catalog in the plugins { } block of a Kotlin script

8.1

Use the plugin aliases of the catalog (alias(libs.plugins.x)).

The eager accessors of configurations in the Kotlin DSL (configurations.compileClasspath.files)

8.0 (public API), kept for plugins compiled against older Gradle

configurations.compileClasspath.get().files.

kotlinDslPluginOptions.jvmTarget

-

A Java toolchain.

The shorthand gradle-enterprise in plugins { } of settings.gradle.kts

8.8

id("com.gradle.develocity") version "..." (the plugin was renamed from Gradle Enterprise to Develocity).

WriteProperties.outputFile

8.0

destinationFile.

org.gradle.cache.cleanup, buildCache.local.removeUnusedEntriesAfterDays

8.1, 8.8

The cache cleanup settings of the Gradle user home, configured in an init script.

Unix mode integers for file permissions (fileMode, dirMode, FileCopyDetails.setMode)

8.8

filePermissions { } and dirPermissions { }, available from 8.3.

org.gradle.util.CollectionUtils, ConfigureUtil, ClosureBackedAction; JvmVendorSpec.IBM_SEMERU; GroovySourceSet and ScalaSourceSet; IdeaModule.testSourceDirs

8.x

Action parameters (Gradle generates the closure overloads), JvmVendorSpec.IBM, the source directory sets of the plugins, testSources.

The Groovy modules groovy-test, groovy-console and groovy-sql from the distribution; the model and components tasks of the software model (added only when a rule-based plugin is applied)

-

Declare the Groovy modules as dependencies of your own build logic.

The failures are not all alike. In the Groovy DSL a removed method or property is a runtime error when the line is evaluated, and the message names the receiver; in the Kotlin DSL it is a compilation error of the whole script, listed all at once. Real outputs from 9.8.1 for the removals above (Kotlin DSL first, then the Groovy DSL):

Kotlin DSL: repositories { jcenter() }
* Where:
Build file '<work>/child/build.gradle.kts' line: 2

* What went wrong:
Script compilation error:

  Line 2: repositories { jcenter() }
                         ^ Unresolved reference 'jcenter'.

1 error
Groovy DSL: repositories { jcenter() }
* Where:
Build file '<work>/child/build.gradle' line: 2

* What went wrong:
A problem occurred evaluating project ':child'.
> Could not find method jcenter() for arguments [] on repository container of type org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.
Kotlin DSL: sourceCompatibility = JavaVersion.VERSION_17 (a convention property)
* Where:
Build file '<work>/child/build.gradle.kts' line: 2

* What went wrong:
Script compilation error:

  Line 2: sourceCompatibility = JavaVersion.VERSION_17
          ^ Unresolved reference 'sourceCompatibility'.

1 error
Kotlin DSL: println(convention)
* Where:
Build file '<work>/child/build.gradle.kts' line: 1

* What went wrong:
Script compilation error:

  Line 1: println(convention)
                  ^ Unresolved reference 'convention'.

1 error
Groovy DSL: println convention
* Where:
Build file '<work>/child/build.gradle' line: 2

* What went wrong:
A problem occurred evaluating project ':child'.
> Could not get unknown property 'convention' for project ':child' of type org.gradle.api.Project.
Kotlin DSL: exec { commandLine("echo", "x") }
* Where:
Build file '<work>/child/build.gradle.kts' line: 1

* What went wrong:
Script compilation errors:

  Line 1: exec { commandLine("echo", "x") }
          ^ Unresolved reference 'exec'.

  Line 1: exec { commandLine("echo", "x") }
                 ^ Unresolved reference 'commandLine'.

2 errors
Groovy DSL: exec { commandLine 'echo', 'x' }
* Where:
Build file '<work>/child/build.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':child'.
> Could not find method exec() for arguments [build_dsrlfywjlncks6y799wq6vzmc$_run_closure1@1c824407] on project ':child' of type org.gradle.api.Project.
Kotlin DSL: tasks { "help"() }
* Where:
Build file '<work>/child/build.gradle.kts' line: 1

* What went wrong:
Script compilation error:

  Line 1: tasks { "help"() }
                        ^ No value passed for parameter 'configuration'.

1 error

The error of the last one does not say "removed": the notation "help"() without a lambda is the one that went away, and the Kotlin compiler sees the remaining overload, "name"(configuration), so the message is about the missing parameter. The command-line options show the same problem in a third form, since the unknown option message also prints the usage of the command:

$ ./gradlew -b build.gradle hello --console=plain
Unknown command-line option '-b'.

To see more detail about a task, run gradlew help --task <task>
For example, gradlew help --task build.gradle

The two other things a reader of an old build file looks for are the dependency configurations and the plugins. They were removed long before 9.0, in 7.0, and are in the table of the older majors below; on 9.8.1 they fail as:

Kotlin DSL: dependencies { compile("info.picocli:picocli:4.7.7") }
* Where:
Build file '<work>/child/build.gradle.kts' line: 2

* What went wrong:
Script compilation error:

  Line 2: dependencies { compile("info.picocli:picocli:4.7.7") }
                         ^ Unresolved reference 'compile'.

1 error

Behavior changes that do not fail

The dangerous changes of a major release are the ones that keep the build green. The 9.0.0 guide has several; each of the next three was reproduced.

Archives are reproducible by default. The tasks Jar, War, Ear, Zip and every AbstractArchiveTask now sort entries deterministically, give every file a fixed timestamp (1 February 1980 for ZIP-based archives; the exact value depends on the archive type) and permissions 0644, and every directory 0755. Before 9.0 the order depended on the file system and the times and permissions were those of the files. The change helps builds that hash their outputs or compare them between machines (see Supply-Chain Security and, for Maven’s project.build.outputTimestamp, Maven Security & Reproducible Builds), and surprises builds that read the time or the permissions back, such as a distribution whose scripts must be executable. Restoring the old behavior is per property, or for permissions with the Gradle property org.gradle.archives.use-file-system-permissions=true. Two source files with different permissions and times (shown with stat), zipped by two tasks, one with the new defaults and one restoring the old behavior:

$ stat -f '%Sp %Sm %N' -t '%Y-%m-%d %H:%M' files/*
-rw-r----- 2022-05-06 07:08 files/alpha.txt
-rw------- 2020-01-02 03:04 files/zeta.txt
Kotlin DSL: build.gradle.kts
val reproducible = tasks.register<Zip>("reproducibleZip") {
    from("files")
    archiveFileName = "reproducible.zip"
    destinationDirectory = layout.buildDirectory.dir("out")
}

val legacy = tasks.register<Zip>("legacyZip") {
    from("files")
    archiveFileName = "legacy.zip"
    destinationDirectory = layout.buildDirectory.dir("out")
    isReproducibleFileOrder = false
    isPreserveFileTimestamps = true
    useFileSystemPermissions()
}
Groovy DSL: build.gradle
tasks.register('reproducibleZip', Zip) {
    from 'files'
    archiveFileName = 'reproducible.zip'
    destinationDirectory = layout.buildDirectory.dir('out')
}

tasks.register('legacyZip', Zip) {
    from 'files'
    archiveFileName = 'legacy.zip'
    destinationDirectory = layout.buildDirectory.dir('out')
    reproducibleFileOrder = false
    preserveFileTimestamps = true
    useFileSystemPermissions()
}

The two builds were run with ./gradlew reproducibleZip legacyZip --configuration-cache and produced the same archives (the Groovy names of the two properties, reproducibleFileOrder and preserveFileTimestamps, differ from the Kotlin is... names because a Boolean property in the Kotlin DSL is written with its getter name). The entries as stored:

$ zipinfo -l reproducible.zip
Archive:  reproducible.zip
Zip file size: 221 bytes, number of entries: 2
-rw-r--r--  2.0 unx        3 b-        5 defN 80-Feb-01 00:00 alpha.txt
-rw-r--r--  2.0 unx        6 b-        8 defN 80-Feb-01 00:00 zeta.txt

$ zipinfo -l legacy.zip
Archive:  legacy.zip
Zip file size: 221 bytes, number of entries: 2
-rw-r-----  2.0 unx        3 b-        5 defN 22-May-06 07:08 alpha.txt
-rw-------  2.0 unx        6 b-        8 defN 20-Jan-02 03:04 zeta.txt

The first archive carries 80-Feb-01 00:00 and -rw-r--r-- for both files, whatever the files were; the second keeps the permissions and times of the sources (-rw-r----- and -rw-------, 2022 and 2020).

A Test task without configuration no longer runs the tests of the test source set. A Test task that was registered with no configuration used to take its classes and classpath from the test source set by convention (deprecated in 8.1); 9.0 removes the convention, and the build does not fail. The task is skipped because it has no test classes, so a project can stop running a whole test suite without a single warning. The scratch build has one JUnit Jupiter test and registers otherTest with nothing else:

Kotlin DSL: build.gradle.kts
plugins { java }

repositories { mavenCentral() }

testing.suites.named<JvmTestSuite>("test") { useJUnitJupiter() }

tasks.register<Test>("otherTest")
Groovy DSL: build.gradle
plugins { id 'java' }

repositories { mavenCentral() }

testing.suites.test { useJUnitJupiter() }

tasks.register('otherTest', Test)
$ ./gradlew test otherTest --console=plain --configuration-cache
Calculating task graph as no cached configuration is available for tasks: test otherTest
> Task :otherTest NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :processResources NO-SOURCE
> Task :compileJava NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :testClasses
> Task :test

BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed
Configuration cache entry stored.

:otherTest NO-SOURCE is the whole symptom: the Groovy DSL build printed the same. The fix is to configure the task explicitly: the classes directory and the classpath of the suite, and the test framework (a Test task has no framework by default, so without useJUnitPlatform() the next error appears):

Kotlin DSL: build.gradle.kts
plugins { java }

repositories { mavenCentral() }

testing.suites.named<JvmTestSuite>("test") { useJUnitJupiter() }

val unit = testing.suites.named<JvmTestSuite>("test")

tasks.register<Test>("otherTest") {
    useJUnitPlatform()
    testClassesDirs = files(unit.map { it.sources.output.classesDirs })
    classpath = files(unit.map { it.sources.runtimeClasspath })
}
Groovy DSL: build.gradle
plugins { id 'java' }

repositories { mavenCentral() }

testing.suites.test { useJUnitJupiter() }

tasks.register('otherTest', Test) {
    useJUnitPlatform()
    testClassesDirs = testing.suites.test.sources.output.classesDirs
    classpath = testing.suites.test.sources.runtimeClasspath
}
$ ./gradlew otherTest --console=plain --configuration-cache --warning-mode=fail
Calculating task graph as no cached configuration is available for tasks: otherTest
> Task :processTestResources NO-SOURCE
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :testClasses UP-TO-DATE
> Task :otherTest

BUILD SUCCESSFUL in 16s
2 actionable tasks: 1 executed, 1 up-to-date
Configuration cache entry stored.

The guide’s own Kotlin example uses the delegate val test by testing.suites.existing(JvmTestSuite::class); the named form above is used instead, because the delegates are deprecated since 9.6 (see below). The better fix is usually not a hand-made Test task at all but a second suite, testing.suites.register<JvmTestSuite>("integrationTest"), covered on Testing JVM Projects.

A test task that finds no tests fails. When test sources exist, no filter is applied and the task runs but discovers nothing, 9.0 fails the build; this catches the classic migration mistake of JUnit 4 test classes under a JUnit Jupiter engine. A scratch build with a JUnit 4 test class and useJUnitJupiter():

Kotlin DSL: build.gradle.kts
plugins { java }

repositories { mavenCentral() }

dependencies { testImplementation("junit:junit:4.13.2") }

testing.suites.named<JvmTestSuite>("test") { useJUnitJupiter() }
Groovy DSL: build.gradle
plugins { id 'java' }

repositories { mavenCentral() }

dependencies { testImplementation 'junit:junit:4.13.2' }

testing.suites.test { useJUnitJupiter() }
> Task :testClasses
> Task :test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There are test sources present and no filters are applied, but the test task did not discover any tests to execute. This is likely due to a misconfiguration. Please check your test configuration. If this is not a misconfiguration, this error can be disabled by setting the 'failOnNoDiscoveredTests' property to false.

The Groovy DSL build failed with the same message. The error names the escape hatch, failOnNoDiscoveredTests = false on the task (with tasks.withType<AbstractTestTask>().configureEach { failOnNoDiscoveredTests = false } for all of them), but the right fix is the one the message hints at: either add the Vintage engine (org.junit.vintage:junit-vintage-engine) or move the tests to Jupiter, or select the framework that the tests use.

The other changes of this kind, in the order of the guide:

  • assemble stops building artifacts of arbitrary configurations. The assemble task used to build the artifacts of every configuration whose visible flag was not false, and the archives configuration collected them. Both were removed in 9.0; a custom artifact now needs tasks.named("assemble") { dependsOn(theArtifactTask) }. The archives configuration (deprecated for artifact declaration in 9.1) and Configuration.visible (deprecated, to be removed in Gradle 11) remain only as deprecated shells.

  • Packaging plugins combine. The ear and war plugins no longer suppress each other and the jar: applying java, war and ear builds a JAR, a WAR and an EAR with assemble.

  • Stale class files outside the build directory are no longer deleted, a special case of source set outputs that forced every compile task to be created eagerly.

  • The signing plugin writes OpenPGP signatures that match the version of the key (RFC 9580, version 6 keys); before 9.0 it always wrote version 4 signatures.

  • Gradle Module Metadata that is modified after an eagerly created publication is an error instead of a warning.

  • ValidatePlugins needs the toolchain plugin (jvm-toolchains, applied by every JVM plugin) and, since 9.2, a Java version that the daemon supports.

  • The default versions of the bundled tools moved: Checkstyle 10.24.0, CodeNarc 3.6.0, PMD 7.13.0, JUnit Jupiter 5.12.2, TestNG 7.11.0, Spock 2.3 and Eclipse JGit 7.2.1. A new Checkstyle or PMD version brings new rules, so a quality gate that was green can turn red without a source change; pin the tool version (toolVersion) while upgrading and move it afterwards as a separate step.

  • JavaExec uses the toolchain of the java extension when java-base is applied (announced in the 8.14 guide), where it used the Java of the daemon before. A build that sets no toolchain is unaffected; a build that does, and runs a JavaExec or the run task, now runs on the toolchain’s Java.

  • Project directories must exist. An include of a project without a directory (or with a read-only one) fails. Deprecated in 8.5, an error from 9.0:

FAILURE: Build failed with an exception.

* What went wrong:
Configuring project with invalid directory
  Configuring project ':missing-module' without an existing directory is not allowed. The configured projectDirectory '<work>/missing-module' does not exist, can't be written to or is not a directory.
    For more information, please refer to https://docs.gradle.org/9.8.1/userguide/multi_project_builds.html#include_existing_projects_only.
    Possible solution: Make sure the project directory exists and is writable.

The configuration cache in Gradle 9

Gradle 9.0.0 does not turn the configuration cache on by default (that is announced for Gradle 10), but it makes it the preferred mode and tightens three things that matter to a migration. An unsupported provider passed to BuildEventsListenerRegistry.onTaskCompletion used to be silently dropped when the cache was on and is now a configuration cache problem that fails the build; the property org.gradle.configuration-cache.unsafe.ignore.unsupported-build-events-listeners=true restores the old behavior for now and will be removed in Gradle 10. A cache entry is always discarded when the work graph contains a task marked incompatible, even in the warn problems mode, which used to store it. And the deprecations about reaching the build model from a task action (Task.project, Task.getExtensions(), the task dependencies, an injected Project or Gradle) are warnings without the cache and failures with it, as this single task shows (Kotlin DSL; the Groovy DSL fails the same way):

> Task :child:t
Invocation of Task.project at execution time has been deprecated. This will fail with an error in Gradle 10. This API is incompatible with the configuration cache, which will become the only mode supported by Gradle in a future release. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_7.html#task_project
child

BUILD SUCCESSFUL in 497ms
1 actionable task: 1 executed
With --configuration-cache
> Task :child:t FAILED

1 problem was found storing the configuration cache.
- Task `:child:t` of type `org.gradle.api.DefaultTask`: invocation of 'Task.project' at execution time is unsupported with the configuration cache.
  See https://docs.gradle.org/9.8.1/userguide/configuration_cache_requirements.html#config_cache:requirements:use_project_during_execution

See the complete report at file://<work>/build/reports/configuration-cache/8zpfyesyt1kunq46g1mptil9z/avrturodvhz6l3z2qoghbvn6m/configuration-cache-report.html

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':child:t' (registered in build file 'child/build.gradle.kts').
> Invocation of 'Task.project' by task ':child:t' at execution time is unsupported with the configuration cache.

Moving the model reads to configuration time (val projectName = project.name outside the doLast) fixes both. The cache itself is on Configuration Cache & Isolated Projects.

What 8.x deprecated and 9.0 removed or changed (not run here)

Because 8.14.x was not available, the correspondence between the last 8.x line and the removals above is described from the 8.x guide. The 8.x line had these deprecation warnings that were turned into removals or behavior changes, and every one of them was visible with --warning-mode=all on 8.14:

Deprecated in Warning in 8.x Outcome in 9.0

8.0

WriteProperties.outputFile; eager Kotlin DSL configuration accessors (public API)

removed

8.1

custom build layout through the StartParameter and GradleBuild properties (the -b/-c options themselves were deprecated in 7.1); the cache.cleanup property; version catalog libraries in the Kotlin plugins block; Test tasks relying on the test source set convention; running the test task with no tests executed; IBM_SEMERU

removed, or behavior change

8.2

plugin conventions (base, java, application, war, ear, project-report)

conventions removed

8.3

Project.buildDir (replaced by layout.buildDirectory), no warning printed to give plugin authors time

still present in 9.8.1 as a deprecated Kotlin property (the compiler warns, Gradle does not)

8.5

including a project without a directory

error

8.7

"name"() in the Kotlin DSL; plugin conventions registration

removed

8.8

Unix permission integers; the build cache retention setting; the gradle-enterprise plugin shorthand

removed

8.10

running Gradle on older JVMs (announcement)

Java 17 required

8.11

Project.exec, Project.javaexec; mutating buildscript configurations; customized Gradle logging; the ForkOptions javaHome property (undeprecated in 8.14)

exec removed

8.12

Task.getProject() from a task action; the Groovy "space assignment" syntax (propName value)

still deprecated in 9.8.1, error planned for Gradle 10

8.13

Boolean properties with is prefix

Groovy 4 stops recognizing them; Gradle stops in Gradle 10

8.14

Settings.getDefaults() removed in favor of Settings.defaults(Action); the wrapper JAR became executable; JavaExec toolchain default announced; SLF4J 2.0.17

9.0 as announced

The 8.14 line of the table is the reason the 9.0 guide asks for the latest 8.x release first: 8.14 is the release whose deprecation messages are complete. The property of a deprecation warning that matters in practice is that it exists on the old release; on 9.0 the same code is just an error.

Within Gradle 9: from 9.0.0 to 9.8.1

Gradle 9 has been released every six to eleven weeks, and unlike the move to 9.0.0 the minor releases rarely break a clean build, but each one adds deprecations, and an upgrade guide section with "potential breaking changes". The table is the guide for 9.0.0 to 9.8.1 in one place, release by release; dates are those of the release history. The right-hand column is what to look for in the output of --warning-mode=all after moving to that release.

Release Potential breaking changes Deprecations introduced

9.1.0 (2025-09-18)

ASM 9.8 (Java 25), Groovy 4.0.28

multi-string dependency notation; archives configuration; Configuration.visible; JavaForkOptions.setAllJvmArgs; ReportingExtension.file(String) and getApiDocTitle(); non-String GradleBuild project properties; toolchain settings passed with -P

9.2.0 (2025-10-29)

Kotlin 2.2.20; the incubating ObjectFactory#dependencyCollector() removed; consumable configurations of the bundled plugins are initialized lazily (a configurations.named("apiElements").configure { } no longer runs unless the configuration is used); ValidatePlugins needs a Java the daemon supports

Project.container(...); the RuleSource forms of component metadata and selection rules; registerFeature before the Java plugin is applied

9.3.0 (2026-01-16)

Project instances of the same project are no longer guaranteed to be the same object (===, == in Java and .is() in Groovy: compare paths); TestNG before 6.9.13.3 reports test names differently; Kotlin 2.2.21, Groovy 4.0.29, JaCoCo 0.8.14, ASM 9.9, Jansi 2.4.2

Wrapper.getAvailableDistributionTypes(); publishing a project that depends on an unpublished project; legacy Usage attribute values (java-api-jars and others); depending on the current project by module coordinates; conversion of ModuleVersionSelector

9.4.0 (2026-03-04)

ProjectBuilder anchors build-scoped locations to the project directory; java-gradle-plugin puts gradleApi() in compileOnlyApi; stricter validation for published plugins; CodeNarc gets the compilation classpath by default (so it waits for compilation); wrapper system property priority; Kotlin 2.3.0, Zinc 1.12.0

DomainObjectCollection.findAll(Closure); Closure methods of Test (onOutput, beforeTest, afterTest, beforeSuite and others); apply false and version in precompiled script plugins; Dependencies.getProject()

9.5.0 (2026-04-28)

Plugin requests of precompiled settings plugins are validated at compile time; the Windows start script was reworked; System.getenv().values() is now a configuration cache input (changing any variable invalidates the entry); lockfiles use LF line endings; outgoingVariants and resolvableConfigurations hide variants without attributes (use --all); Kotlin 2.3.20

the exitEnvironmentVar of CreateStartScripts

9.6.0 (2026-06-18)

AGP 8.x no longer works (it used removed Problems API internals); concurrency primitives (locks, latches) can no longer be serialized by the configuration cache; PMD 7.24.0, CodeNarc 3.7.0, Ant 1.10.17, Groovy 4.0.32, Kotlin 2.3.21

implicit lookup of properties in parent projects; Develocity plugin before 4.0; getProperties(); Kotlin DSL delegated properties; reading task dependencies, extensions or an injected Project from a task action; Task argument of dependsOn closures; undeclared Artifact Transform inputs; Project objects as dependency notation; artifactUrls; flatDir(Map) and mavenCentral(Map); buildNeeded and buildDependents; IDE file generation tasks; PMD targetJdk; ProblemSpec.severity()

9.7.0 (2026-08-06)

Resilient sync reports failed operations; no checksum files for signature files when publishing; the project cache directory is not watched; Kotlin DSL scripts cannot import org.gradle.internal.impldep.*; *Parameters.None types behave the same everywhere; Kotlin 2.4.0

the software model; the org.gradle.unsafe.isolated-projects* property names; custom collection and map types in configuration cache state

9.8.0 (2026-09-24)

Task logging listeners are stored in the configuration cache; Kotlin object singletons keep identity but not state; invalid toolchain installation paths warn instead of failing; Copy/Sync destination is the lazy destinationDirectory (both outputs are registered in 9.x); Kotlin 2.4.10, Groovy 4.0.33, ASM 9.10.1, JaCoCo 0.8.15

implicit opt-in to parallel model building; starting external processes at configuration time; enabling the build cache from build logic; implicit task dependencies between builds; StartParameter construction; Signature as an artifact and its constructors; unsupported attribute value types; macOS on AMD64

9.8.1 (2026-10-07)

a patch release: three high-severity security fixes (deserialization in daemon and worker communication; repositories that fail to establish an SSL connection), toolchain validation on Debian and Ubuntu, a java.util.logging regression and a dependency resolution regression

none

Two entries in the table are not what a reader expects, and both could be reproduced. The 9.6 guide, "implicit lookup of properties in parent projects", is the deprecation with the widest effect, because a build that stores a shared value as an extra property (extra["greeting"], ext.greeting) of the root project and reads it by its bare name in a subproject has been doing the same thing for ten years. And "Kotlin DSL delegated properties" deprecates, in one go, the forms val x by tasks.registering, val x: String by project, val x by extra and the delegates of Property, so a large part of the Kotlin DSL code written between 5.0 and 9.5 produces warnings.

A build with ten deprecations, before and after

The scratch build below has a root project that stores greeting as an extra property and a subproject child that does what the guides call out: reads the property through a delegate (Kotlin) or by bare name (Groovy), declares a dependency with the multi-string notation and with a Project object, registers a task through a delegate, adds an artifact to the archives configuration, runs a process while the build is configured, and reads the project from a task action. The Groovy version also uses the space-assignment syntax and findAll. The root build.gradle.kts is extra["greeting"] = "hello" and the root build.gradle is ext.greeting = 'hello'; there is a second subproject core with the java-library plugin.

Kotlin DSL: child/build.gradle.kts (before)
plugins { `java-library` }

val greeting: String? by project

val coreProject = project(":core")

dependencies {
    implementation(group = "org.apache.commons", name = "commons-csv", version = "1.14.1")
    implementation(coreProject)
}

val specialJar by tasks.registering(Jar::class) {
    archiveBaseName = "special"
}

artifacts { add("archives", specialJar) }

val gitRev = ProcessBuilder("echo", "abc123").start().inputStream.bufferedReader().readText().trim()

tasks.register("report") {
    doLast { println("greeting=$greeting rev=$gitRev in ${project.name}") }
}
Groovy DSL: child/build.gradle (before)
plugins { id 'java-library' }

group 'io.tidepool'

def coreProject = project(':core')

dependencies {
    implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.14.1'
    implementation coreProject
}

def specialJar = tasks.register('specialJar', Jar) {
    archiveBaseName = 'special'
}

artifacts { archives specialJar }

def checks = tasks.findAll { it.name.startsWith('check') }

tasks.register('report') {
    doLast { println "greeting=${greeting} checks=${checks*.name} in ${project.name}" }
}

Run with ./gradlew :child:report --warning-mode=all --no-configuration-cache, Kotlin DSL first (the long Kotlin compiler w: lines are shortened at 200 characters where a line ends in ..., and the Gradle messages are complete):

$ ./gradlew :child:report --warning-mode=all --no-configuration-cache --console=plain
> Configure project :child
w: file://<work>/child/build.gradle.kts:3:26: 'fun Project.provideDelegate(any: Any?, property: KProperty<*>): PropertyDelegate' is deprecated. Use 'val property = project.property(name)' or 'val prop ...
w: file://<work>/child/build.gradle.kts:8:5: 'fun DependencyHandler.implementation(group: String?, name: String?, version: String? = ..., configuration: String? = ..., classifier: String? = ..., ext:  ...
w: file://<work>/child/build.gradle.kts:12:25: 'fun <U : Task> RegisteringDomainObjectDelegateProviderWithTypeAndAction<out TaskContainer, U>.provideDelegate(receiver: Any?, property: KProperty<*>): E ...
w: file://<work>/child/build.gradle.kts:12:25: 'fun <T : Any, C : PolymorphicDomainObjectContainer<T>, U : T> C.registering(type: KClass<U>, action: U.() -> Unit): RegisteringDomainObjectDelegateProvi ...
w: file://<work>/child/build.gradle.kts:12:25: 'fun <T> ExistingDomainObjectDelegate<out T>.getValue(receiver: Any?, property: KProperty<*>): T' is deprecated. Use the named() API instead. See the Gra ...
The 'val name: Type? by project' property delegate syntax has been deprecated. This is scheduled to be removed in Gradle 10. Use 'val property = project.findProperty(name)' instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#kotlin_dsl_delegated_properties
Declaring dependencies using multi-string notation has been deprecated. This will fail with an error in Gradle 10. Please use single-string notation ("org.apache.commons:commons-csv:1.14.1") or DependencyFactory instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#dependency_multi_string_notation
Using a Project object as a dependency notation has been deprecated. This will fail with an error in Gradle 10. Please use the project(String) method on DependencyHandler or the createProjectDependency(String) method on DependencyFactory instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#dependency_project_notation
The 'val name by registering(Type::class) { }' property delegate syntax has been deprecated. This is scheduled to be removed in Gradle 10. Use 'val element = register<Type>(name) { }' instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#kotlin_dsl_delegated_properties
The archives configuration has been deprecated for artifact declaration. This will fail with an error in Gradle 10. Add artifacts as direct task dependencies of the 'assemble' task instead of declaring them in the archives configuration. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#sec:archives-configuration
Starting an external process at configuration time has been deprecated. This will fail with an error in Gradle 11. Use ProviderFactory.exec/javaexec or a ValueSource to obtain the process output, or move the process execution into a task. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#config_time_external_processes

> Task :child:report
Implicit lookup of properties in parent projects has been deprecated. This will fail with an error in Gradle 10. Property 'greeting' was not declared in project ':child' and was resolved from root project 'tidepool'. This lookup was initiated by 'val ... by project'. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecated_implicit_lookup_in_parent_projects
Invocation of Task.project at execution time has been deprecated. This will fail with an error in Gradle 10. This API is incompatible with the configuration cache, which will become the only mode supported by Gradle in a future release. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_7.html#task_project
greeting=hello rev=abc123 in child

BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

And the Groovy DSL:

$ ./gradlew :child:report --warning-mode=all --no-configuration-cache --console=plain
> Configure project :child
Build file '<work>/child/build.gradle': line 3
Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated. This is scheduled to be removed in Gradle 10. Use assignment ('group = <value>') instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax
Build file '<work>/child/build.gradle': line 8
Declaring dependencies using multi-string notation has been deprecated. This will fail with an error in Gradle 10. Please use single-string notation ("org.apache.commons:commons-csv:1.14.1") or DependencyFactory instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#dependency_multi_string_notation
Build file '<work>/child/build.gradle': line 9
Using a Project object as a dependency notation has been deprecated. This will fail with an error in Gradle 10. Please use the project(String) method on DependencyHandler or the createProjectDependency(String) method on DependencyFactory instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#dependency_project_notation
Build file '<work>/child/build.gradle': line 16
The archives configuration has been deprecated for artifact declaration. This will fail with an error in Gradle 10. Add artifacts as direct task dependencies of the 'assemble' task instead of declaring them in the archives configuration. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#sec:archives-configuration
Build file '<work>/child/build.gradle': line 18
The DomainObjectCollection.findAll(Closure) method has been deprecated. This is scheduled to be removed in Gradle 10. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#findAll_removal

> Task :child:report
Build file '<work>/child/build.gradle': line 21
Implicit lookup of properties in parent projects has been deprecated. This will fail with an error in Gradle 10. Property 'greeting' was not declared in project ':child' and was resolved from root project 'tidepool'. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecated_implicit_lookup_in_parent_projects
Build file '<work>/child/build.gradle': line 21
Invocation of Task.project at execution time has been deprecated. This will fail with an error in Gradle 10. This API is incompatible with the configuration cache, which will become the only mode supported by Gradle in a future release. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_7.html#task_project
greeting=hello checks=[check] in child

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

Every message maps to a section of the 9.x guide through the link at its end, and to a replacement in the message. In the order of the Kotlin output:

Warning Fix Since

val greeting: String? by project (twice: the delegate and the implicit lookup)

providers.gradleProperty("greeting") with the value in gradle.properties, or project.findProperty("greeting"); a value that must come from the root project is written explicitly (rootProject.extra) until it is moved to a convention plugin or gradle.properties

9.6

implementation(group = ..., name = ..., version = ...)

implementation("org.apache.commons:commons-csv:1.14.1"), or project.dependencyFactory.create(group, name, version) when the parts come from variables

9.1

implementation(coreProject)

implementation(project(":core"))

9.6

val specialJar by tasks.registering(Jar::class)

val specialJar = tasks.register<Jar>("specialJar") { ... }

9.6

artifacts { add("archives", specialJar) } (the link in this 9.8.1 message ends in #sec:archives-configuration, an anchor that the page does not have; the section is #archives-configuration)

tasks.named("assemble") { dependsOn(specialJar) }

9.1

ProcessBuilder(...).start() while configuring

providers.exec { commandLine(...) }.standardOutput.asText, or move the process into a task

9.8 (error in Gradle 11)

project.name in doLast (Task.project)

read it at configuration time into a local val

8.12, and an error with the configuration cache

The Groovy rows are the same mapping plus two of its own: the space assignment group 'io.tidepool' becomes group = 'io.tidepool' (deprecated in 8.12), and tasks.findAll { ... } becomes tasks.matching { ... } (9.4). The fixed builds follow; they print no deprecation under --warning-mode=fail, and run with the configuration cache, which is what makes the second run of each (Reusing configuration cache) meaningful. The value now comes from gradle.properties (greeting=hello) and the root build file is gone.

Kotlin DSL: child/build.gradle.kts (after)
plugins { `java-library` }

val greeting = providers.gradleProperty("greeting")

dependencies {
    implementation("org.apache.commons:commons-csv:1.14.1")
    implementation(project(":core"))
}

val specialJar = tasks.register<Jar>("specialJar") {
    archiveBaseName = "special"
}

tasks.named("assemble") { dependsOn(specialJar) }

val gitRev = providers.exec { commandLine("echo", "abc123") }.standardOutput.asText.map { it.trim() }

tasks.register("report") {
    val greeting = greeting
    val gitRev = gitRev
    val projectName = project.name
    doLast { println("greeting=${greeting.get()} rev=${gitRev.get()} in $projectName") }
}
Groovy DSL: child/build.gradle (after)
plugins { id 'java-library' }

group = 'io.tidepool'

def greeting = providers.gradleProperty('greeting')

dependencies {
    implementation 'org.apache.commons:commons-csv:1.14.1'
    implementation project(':core')
}

def specialJar = tasks.register('specialJar', Jar) {
    archiveBaseName = 'special'
}

tasks.named('assemble') { dependsOn(specialJar) }

def checks = tasks.matching { it.name.startsWith('check') }

tasks.register('report') {
    def projectName = project.name
    def greetingValue = greeting
    def checkNames = checks.collect { it.name }
    doLast { println "greeting=${greetingValue.get()} checks=${checkNames} in ${projectName}" }
}

The first run of each, with ./gradlew :child:report --warning-mode=fail --configuration-cache, Kotlin DSL then Groovy DSL:

$ ./gradlew :child:report --warning-mode=fail --configuration-cache --console=plain
Calculating task graph as no cached configuration is available for tasks: :child:report

> Task :child:report
greeting=hello rev=abc123 in child

BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed
Configuration cache entry stored.
$ ./gradlew :child:report --warning-mode=fail --configuration-cache --console=plain
Calculating task graph as configuration cache cannot be reused because file 'child/build.gradle' has changed.

> Task :child:report
greeting=hello checks=[check] in child

BUILD SUCCESSFUL in 643ms
1 actionable task: 1 executed
Configuration cache entry stored.

Both are compatible with the configuration cache and free of deprecations; the second run of the Kotlin build reused the entry:

$ ./gradlew :child:report --warning-mode=fail --configuration-cache --console=plain
Reusing configuration cache.

> Task :child:report
greeting=hello rev=abc123 in child

BUILD SUCCESSFUL in 604ms
1 actionable task: 1 executed
Configuration cache entry reused.

Deprecations that need a single line to reproduce

The remaining 9.x deprecations are easier to see one at a time. Each of the following was produced by a one-line build file in a subproject child (Kotlin DSL unless it says Groovy), with --warning-mode=all --no-configuration-cache; only the new line of output is shown.

Groovy "space assignment" (8.12, Gradle 10). In regular Groovy propName value is a method call; Gradle generates the method, and the generated form is going away. Write propName = value; where a real method exists, write propName(value).

Build file '<work>/child/build.gradle': line 1
Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated. This is scheduled to be removed in Gradle 10. Use assignment ('group = <value>') instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax
Build file '<work>/child/build.gradle': line 2
Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated. This is scheduled to be removed in Gradle 10. Use assignment ('version = <value>') instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax

Project.getProperties() (9.6, Gradle 10). The map merges properties from many places and walks the parent chain; providers.gradleProperty("name") returns a typed provider of a Gradle property, project.property("name") or findProperty one value.

The Project.getProperties method has been deprecated. This will fail with an error in Gradle 10. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecated_get_properties

Implicit lookup in parent projects (9.6, Gradle 10). A subproject read a property that only the root project defines. The messages for the Kotlin delegate and for a Groovy bare name:

The 'val name: Type? by project' property delegate syntax has been deprecated. This is scheduled to be removed in Gradle 10. Use 'val property = project.findProperty(name)' instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#kotlin_dsl_delegated_properties
Implicit lookup of properties in parent projects has been deprecated. This will fail with an error in Gradle 10. Property 'greeting' was not declared in project ':child' and was resolved from root project 'lab'. This lookup was initiated by 'val ... by project'. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecated_implicit_lookup_in_parent_projects
Build file '<work>/child/build.gradle': line 1
Implicit lookup of properties in parent projects has been deprecated. This will fail with an error in Gradle 10. Property 'greeting' was not declared in project ':child' and was resolved from root project 'lab'. This lookup was initiated by a dynamic invocation in the build script. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecated_implicit_lookup_in_parent_projects

The feature preview NO_IMPLICIT_LOOKUP_IN_PARENT_PROJECTS switches a build to the Gradle 10 behavior before the deprecations are all fixed, which prevents new accidental lookups from creeping in (enableFeaturePreview("NO_IMPLICIT_LOOKUP_IN_PARENT_PROJECTS") in the settings file). With it, the same Groovy line is an error, and the Kotlin delegate (whose own deprecation remains) gets null instead of the root’s value:

* Where:
Build file '<work>/child/build.gradle' line: 1

* What went wrong:
A problem occurred evaluating project ':child'.
> Could not get unknown property 'greeting' for project ':child' of type org.gradle.api.Project.
The 'val name: Type? by project' property delegate syntax has been deprecated. This is scheduled to be removed in Gradle 10. Use 'val property = project.findProperty(name)' instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#kotlin_dsl_delegated_properties
null

buildNeeded (9.6, Gradle 10). Running the task prints the deprecation after the build tasks ran (it is the task itself that is deprecated):

> Task :child:buildNeeded
The buildNeeded task has been deprecated. This is scheduled to be removed in Gradle 10. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecate_build_needed_build_dependents_tasks

Enabling the build cache from build logic (9.8, Gradle 10). gradle.startParameter.isBuildCacheEnabled = true in settings.gradle.kts; the supported way is the Gradle property org.gradle.caching=true or --build-cache.

The StartParameter.setBuildCacheEnabled(boolean) method has been deprecated. This is scheduled to be removed in Gradle 10. Use the 'org.gradle.caching' Gradle property to enable or disable the build cache instead. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#deprecation_enabling_build_cache_from_build_logic

Toolchain properties as project properties (9.1, Gradle 10). -Porg.gradle.java.installations.auto-detect=false becomes -Dorg.gradle.java.installations.auto-detect=false; the build still ran:

> Configure project :child
Specifying 'org.gradle.java.installations.auto-detect' as a project property on the command line has been deprecated. This is scheduled to be removed in Gradle 10. Instead, specify it as a Gradle property: '-Dorg.gradle.java.installations.auto-detect=false'. Consult the upgrading guide for further information: https://docs.gradle.org/9.8.1/userguide/upgrading_version_9.html#toolchain-project-properties

Feature preview ENHANCED_GRAPH_ORDERING (9.7). A preview that opts in to the dependency resolution ordering that becomes the default in Gradle 10 (breadth-first for the DEFAULT sort order, a topological CONSUMER_FIRST option, constraint edges ignored for all sort options). enableFeaturePreview("ENHANCED_GRAPH_ORDERING") was accepted by 9.8.1 without output; a misspelled name is an error:

* Where:
Settings file '<work>/settings.gradle.kts' line: 3

* What went wrong:
There is no feature named NO_SUCH_PREVIEW

Announced for Gradle 10

Gradle 10 has not been released; what follows is the list of things that the guides of Gradle 9.8.1 say will change, stated as announced and not as released. The sentence the guides repeat is that the configuration cache "will be enabled by default in Gradle 10", and that is why many deprecations are now reported even when the cache is off: they are the behaviors that the cache cannot support.

Announced change Announced in What to do now

The configuration cache is on by default

9.0 ("preferred mode"), restated in 9.6-9.8

Run the build with --configuration-cache and fix the problems; see the page on the cache.

Implicit lookup of properties and methods in parent projects is removed

9.6

providers.gradleProperty, convention plugins, or explicit rootProject.extra; try the feature preview.

getProperties(), Kotlin DSL delegated properties, the Groovy is prefix on Boolean getters, the space-assignment syntax

9.6, 9.6, 8.13, 8.12

Replace by the explicit forms in the sections above.

Multi-string dependency notation, Project objects as a dependency notation, depending on the current project by module coordinates, legacy Usage values

9.1, 9.6, 9.3, 9.3

Single-string notation, project(":x"), a project dependency on the current project (deps(project) in the guide’s example), Usage plus LibraryElements.

Reading Task.project, task dependencies, extensions or an injected Project or Gradle from a task action; a Task argument in dependsOn { } closures

8.12 and 9.6

Capture values at configuration time or declare task inputs.

archives configuration; Configuration.visible (removal in Gradle 11)

9.1

dependsOn on assemble; delete isVisible.

buildNeeded, buildDependents, Configuration.getTaskDependencyFromProjectDependency

9.6

Run :proj:build, aggregate test reports, or use artifact views.

The IDE file generation tasks (idea, eclipse, openIdea and their clean tasks) and the model properties that affect only generated files

9.6

Import the project from the IDE; the idea { } and eclipse { } model settings that IDEs read remain.

The software model (model { }, RuleSource, the native component plugins c, cpp, native-component and others)

9.7

cpp-application, cpp-library, swift-application, swift-library, cpp-unit-test, xctest.

org.gradle.unsafe.isolated-projects* (replaced by org.gradle.isolated-projects* and --isolated-projects)

9.7

Rename the properties.

Implicit parallel model building from org.gradle.parallel

9.8

Set org.gradle.tooling.parallel explicitly to true or false.

External processes at configuration time with ExecOperations, ProcessBuilder and String.execute() (error in Gradle 11)

9.8

A task, providers.exec, or a ValueSource.

Enabling the build cache from build logic; StartParameter construction; Signature as a publishable artifact

9.8

org.gradle.caching; Gradle.getStartParameter(); sign(File...) on the signing APIs.

Tasks that consume the outputs of a task in another build of a composite build without declaring a dependency

9.8

Declare the dependency (a warning in 9.8, an error in Gradle 10).

Attribute values that are not String, Boolean, Number or Named; the Kotlin Gradle Plugin 2.0.x native bundle attribute

9.8

Make an enum implement Named; use KGP 2.1.0 or newer.

macOS on AMD64 as a supported platform

9.8

Move to Apple silicon; JDK 27 and later are not available for macOS x64.

Publishing a project that depends on an unpublished project

9.3

Publish the dependency or remove it.

The older majors: 4 to 8

An old build rarely sits on 8.14. Builds copied from books and blog posts sit on 1.x to 4.x, and the practical question is what a 4.x build needs. The upgrade guides were written one hop at a time, and the supported way to move is to follow them one hop at a time: first to the last release of the old line, then to the next major, using the deprecation warnings of the old line to find the changes. The table lists, for each hop, the changes that a build file written in the style of the Gradle 1.x and 2.x era notices first (the guides list many more).

Hop Changes that break typical build files

4.10 to 5.0 (2018-11-26)

Gradle 5 needs Java 8 to run (4.x needs Java 7). task name << { } no longer works; use doLast. The publishing { } block is no longer evaluated lazily. Dependencies declared by a POM are separated into compile and runtime scopes. Implicit wildcards in POM exclusions are honored. Annotation processors are added through the annotationProcessor configuration. Memory defaults of the client, daemon and workers were greatly reduced. enableFeaturePreview('IMPROVED_POM_SUPPORT') and 'STABLE_PUBLISHING' are no longer needed (they became the default). The release added the Kotlin DSL 1.0, native BOM import (platform()), the Worker API and tasks.register.

5.6 to 6.0 (2019-11-08)

The FindBugs, JDepend, OSGi, announce and build-announcements, and Compare Gradle Builds plugins are removed. Dependencies should no longer be declared using the compile and runtime configurations (deprecated). The legacy publication system (the maven plugin and uploadArchives) is deprecated in favor of maven-publish and ivy-publish. Gradle Module Metadata is always published, with stricter validation. Maven or Ivy repositories are no longer queried for artifacts without metadata by default. buildSrc is reserved as a project name. Executing Gradle without a settings file and calling afterEvaluate on an evaluated project are deprecated (errors in 7.0).

6.9 to 7.0 (2021-04-09)

The compile, runtime, testCompile and testRuntime configurations are removed (use implementation, api, runtimeOnly, testImplementation, testRuntimeOnly; a Groovy extendsFrom runtime silently creates a configuration named runtime instead of failing). The maven plugin and uploadArchives are removed. jcenter() is deprecated. The experimental JavaScript plugins (coffeescript-base, envjs, javascript-base, jshint, rhino), the Play plugins and the unmaintained JVM plugins are removed. Groovy 3. Executing a build without a settings file, and calling afterEvaluate on an evaluated project, are errors. Dependency locking defaults to a single file per project.

7.6 to 8.0 (2023-02-13)

Warnings that became errors: referencing tasks of an included build with finalizedBy, mustRunAfter or shouldRunAfter; invalid Java toolchain specifications; automatic toolchain download without a repository; changing the test framework after setting its options. Removed legacy APIs: the old ArtifactTransform, IncrementalTaskInputs, JacocoMerge, and many API clean-ups. Plugin, task and extension classes are abstract; the lowest supported Kotlin and Android plugins were raised. Kotlin 1.8.

8.14 to 9.0.0

The previous section.

The first column’s dates are the release dates from the release history. For 4.10 to 5.0, the guide for the 4.x line has the sentence that sums up why a direct jump is risky and what to do instead: upgrade to the latest 4.10.x release first "to get the most useful warnings and deprecations information before moving to 5.0".

Gradle in Action on Gradle 9.8.1

Gradle in Action (Muschko, Manning 2014) targets Gradle 1.7 and the Groovy DSL, and it is still a good book about the model: projects, tasks, the lifecycle, configurations and the Tooling API did not change in kind. What changed is the syntax and the plugin set around them, and the changes can be traced to specific releases. This section takes the build of the book’s style, shaped like the tidepool project, and runs it on 9.8.1 one error at a time. The features the book teaches have their own pages in this section; here the question is only what a reader who types in the book’s example finds out.

The book’s build file on Gradle 9.8.1

The scratch project is a single-project build of the tidepool sources, with the idioms of Gradle 1.7: apply plugin, the maven plugin, archivesBaseName, sourceCompatibility, jcenter(), compile and testCompile, a task defined with <<, and uploadArchives with a Maven deployer.

Groovy DSL: build.gradle (the book’s style)
apply plugin: 'java'
apply plugin: 'maven'

group = 'io.tidepool'
version = '1.0.0'
archivesBaseName = 'tidepool'
sourceCompatibility = 1.7

repositories {
    jcenter()
}

dependencies {
    compile 'info.picocli:picocli:4.7.7'
    testCompile 'junit:junit:4.12'
}

task hello << {
    println 'Hello from Gradle 1.7'
}

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: "file://localhost/tmp/repo")
        }
    }
}

Every error below is the first one that ./gradlew hello printed on 9.8.1 after the previous fix, with the lines before * Try: kept. There is nothing to compare on 8.14 here (not run here), but in every case the cause is a removal from the table of this page, and the order of the errors is the order in which Groovy evaluates the file.

1. apply plugin: 'maven'. The legacy maven plugin was deprecated in 6.0 and removed in 7.0. Delete the line; the replacement, maven-publish, comes with the last step.

* Where:
Build file '<work>/build.gradle' line: 2

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Plugin with id 'maven' not found.

* Try:

2. archivesBaseName. One of the conventions, removed in 9.0 (deprecated in 8.2). The base extension has the setting under another name.

* Where:
Build file '<work>/build.gradle' line: 5

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not set unknown property 'archivesBaseName' for root project 'tidepool' of type org.gradle.api.Project.

* Try:

3. sourceCompatibility. Another convention; the extension is java, and a toolchain is the better statement (see Gradle Toolchains). The replacement uses 17, because a JDK 26 javac no longer compiles for Java 7.

* Where:
Build file '<work>/build.gradle' line: 6

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not set unknown property 'sourceCompatibility' for root project 'tidepool' of type org.gradle.api.Project.

* Try:

4. jcenter(). Deprecated in 7.0 and removed in 9.0; the guide notes that the JCenter repository was redirected to Maven Central in August 2024.

* Where:
Build file '<work>/build.gradle' line: 9

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not find method jcenter() for arguments [] on repository container of type org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.

* Try:

5. compile. Removed in 7.0 (deprecated since 6.0). The rule of thumb of the 7.0 guide: compile becomes implementation unless the dependency is part of the public API of a library, in which case it is api and the java-library plugin is needed.

* Where:
Build file '<work>/build.gradle' line: 13

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not find method compile() for arguments [info.picocli:picocli:4.7.7] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.

* Try:

6. testCompile. Removed in 7.0, replaced by testImplementation. Likewise runtime by runtimeOnly and testRuntime by testRuntimeOnly.

* Where:
Build file '<work>/build.gradle' line: 14

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not find method testCompile() for arguments [junit:junit:4.12] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.

* Try:

7. task hello << { }. The left-shift operator on a task was deprecated in 4.x and removed in 5.0. Groovy now tries to call a method leftShift on the task; the message does not mention the operator, which is why this error confuses people. doLast is the replacement, and tasks.register is the lazy form (4.9).

* Where:
Build file '<work>/build.gradle' line: 17

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not find method leftShift() for arguments [build_9k16nfr5ao6rve5hpmmkb35h3$_run_closure5@1e90fd6b] on task ':hello' of type org.gradle.api.DefaultTask.

* Try:

8. uploadArchives. The uploadArchives task came with the maven plugin and was removed in 7.0 with it. Publishing is the maven-publish plugin, described on Publishing.

* Where:
Build file '<work>/build.gradle' line: 23

* What went wrong:
A problem occurred evaluating root project 'tidepool'.
> Could not find method uploadArchives() for arguments [build_9k16nfr5ao6rve5hpmmkb35h3$_run_closure6@620fd1e3] on root project 'tidepool' of type org.gradle.api.Project.

* Try:

After the eight fixes the build file is the one below, written in the Groovy DSL and then in the Kotlin DSL. It uses the plugins { } block (Gradle 2.1 and the Plugin Portal), the application plugin so that the result can be run, a toolchain, mavenCentral(), the commons-csv dependency that the tidepool sources need (the build without it failed with package org.apache.commons.csv does not exist), and maven-publish to a directory of the build.

Groovy DSL: build.gradle (fixed)
plugins {
    id 'application'
    id 'maven-publish'
}

group = 'io.tidepool'
version = '1.0.0'
base { archivesName = 'tidepool' }

java {
    toolchain { languageVersion = JavaLanguageVersion.of(17) }
}

application { mainClass = 'io.tidepool.cli.Main' }

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.commons:commons-csv:1.14.1'
    implementation 'info.picocli:picocli:4.7.7'
}

tasks.register('hello') {
    doLast {
        println 'Hello from Gradle 9'
    }
}

publishing {
    publications {
        maven(MavenPublication) {
            from components.java
        }
    }
    repositories {
        maven { url = layout.buildDirectory.dir('repo') }
    }
}
Kotlin DSL: build.gradle.kts (fixed)
plugins {
    application
    `maven-publish`
}

group = "io.tidepool"
version = "1.0.0"
base { archivesName = "tidepool" }

java {
    toolchain { languageVersion = JavaLanguageVersion.of(17) }
}

application { mainClass = "io.tidepool.cli.Main" }

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.apache.commons:commons-csv:1.14.1")
    implementation("info.picocli:picocli:4.7.7")
}

tasks.register("hello") {
    doLast {
        println("Hello from Gradle 9")
    }
}

publishing {
    publications {
        register<MavenPublication>("maven") {
            from(components["java"])
        }
    }
    repositories {
        maven { url = layout.buildDirectory.dir("repo").get().asFile.toURI() }
    }
}

Both files ran with ./gradlew hello run --args="data/stations.csv SF" publish --warning-mode=fail --configuration-cache. The Java 17 toolchain was the JetBrains Runtime 17.0.14 that is installed on the machine and detected automatically, while the daemon ran on JDK 26. The Kotlin DSL output follows; the Groovy DSL printed the same tasks, with generatePomFileForMavenPublication and compileJava reported as UP-TO-DATE because the Groovy project had been compiled by an earlier run.

$ ./gradlew hello run --args="data/stations.csv SF" publish --warning-mode=fail --configuration-cache --console=plain
Calculating task graph as no cached configuration is available for tasks: hello run --args=data/stations.csv SF publish

> Task :hello
Hello from Gradle 9

> Task :processResources NO-SOURCE
> Task :generatePomFileForMavenPublication
> Task :compileJava
> Task :classes
> Task :jar
> Task :generateMetadataFileForMavenPublication
> Task :publishMavenPublicationToMavenRepository
> Task :publish

> Task :run
San Francisco: -0.869 m

BUILD SUCCESSFUL in 4s
7 actionable tasks: 7 executed
Configuration cache entry stored.

The plugins that the book uses and Gradle 9.8.1 no longer finds are the next question, and which ones are still bundled. Each id was applied alone, with apply plugin: 'id' in a build file of a scratch project, and the help task was run; the line that Gradle printed for each:

maven -> Plugin with id 'maven' not found.
findbugs -> Plugin with id 'findbugs' not found.
jdepend -> Plugin with id 'jdepend' not found.
jetty -> Plugin with id 'jetty' not found.
sonar -> Plugin with id 'sonar' not found.
sonar-runner -> Plugin with id 'sonar-runner' not found.
osgi -> Plugin with id 'osgi' not found.
announce -> Plugin with id 'announce' not found.
build-announcements -> Plugin with id 'build-announcements' not found.
compare-gradle-builds -> Plugin with id 'compare-gradle-builds' not found.
jshint -> Plugin with id 'jshint' not found.
rhino -> Plugin with id 'rhino' not found.
play -> Plugin with id 'play' not found.

All thirteen are the same failure, and the release that removed each is, from the guides and release notes:

Plugin id Removed in

maven

7.0 (legacy publication system; use maven-publish)

findbugs, jdepend

6.0 (the 5.x guide names the third-party SpotBugs plugin as the alternative to FindBugs)

osgi

6.0

announce, build-announcements

6.0

compare-gradle-builds

6.0 (a Build Scan is the replacement)

jetty

4.0 (deprecated in 3.0; the release notes recommend Gretty, third party)

jshint, rhino

7.0 (the experimental JavaScript plugins; the 5.x guide announced the removal as "the next major release")

play

6.0 or 7.0: the guide for 5.x lists the Play plugins as removed in 6.0, and the guide for 6.x lists "Removal of Play Framework plugins" in 7.0

sonar

3.0 (the legacy Sonar plugin; SonarQube’s own org.sonarqube plugin is the replacement)

sonar-runner

not verified: the release that removed it is not stated in the guides that were read

The plugins that did apply, each in its own run, were java, checkstyle, pmd, jacoco, codenarc, eclipse, idea, war, ear, application, signing, ivy-publish, maven-publish, distribution, scala, groovy, project-report, build-dashboard and cpp-library, which is the answer to "where did the checks of the book go": Checkstyle, PMD and JaCoCo are still bundled, Cobertura never was, and FindBugs and JDepend are not.

The idioms of the book, one by one

Every row of the table is a statement of the book (or a practice it recommends) that is wrong or outdated on Gradle 9.8.1, with the release that changed it and the current form. The rows that were reproduced above say so; the others come from the cited pages.

Book statement or practice Current status Source

task name << { }

Removed in 5.0 (Gradle 5.0 changes: << for task definitions no longer works). Use doLast {} and tasks.register (lazy, 4.9). Reproduced above.

upgrading_version_4

compile, runtime, testCompile, testRuntime

Removed in 7.0; implementation, api, compileOnly, runtimeOnly, testImplementation, testRuntimeOnly. Reproduced above.

upgrading_version_6

apply plugin: 'x' only, and no plugin portal

The plugins { } block and the Gradle Plugin Portal are the standard form; apply plugin: still works for plugins that are already on the classpath, and it is the only form that the Kotlin DSL cannot give type-safe accessors for.

plugins

Groovy DSL only

The Kotlin DSL became 1.0 in 5.0 and the default of gradle init in 8.2 (see Kotlin DSL and Groovy DSL & Migrating to Kotlin).

release notes 8.2

setupBuild (incubating)

The init task of the Build Init plugin (gradle init); see Migrating from Maven.

build_init_plugin

The jetty plugin (jettyRun)

Deprecated in 3.0, removed in 4.0; the third-party Gretty plugin is the usual replacement. Reproduced above.

release notes 4.0

The maven plugin and uploadArchives

Removed in 7.0; maven-publish. Reproduced above.

upgrading_version_6

maven-publish and ivy-publish as incubating

Stable and the standard form of publishing.

publishing_maven

FindBugs, JDepend, announce and build-announcements plugins; the build comparison plugin

Removed in 6.0 (deprecated 5.x); SpotBugs and others are third-party plugins. Reproduced above.

upgrading_version_5

JavaScript plugins (rhino, envjs, coffeescript, jshint)

Removed in 7.0 (the outline of this section had 6.0; the 6.x guide says 7.0). Reproduced above.

upgrading_version_6

The sonar and sonar-runner plugins

The legacy Sonar plugin was removed in 3.0; sonar-runner is not found on 9.8.1 either (the release that removed it was not verified). Use the org.sonarqube plugin of SonarQube. Reproduced above.

release notes 3.0

The Cobertura plugin

Was always third party; the core JaCoCo plugin is the standard. jacoco applies on 9.8.1.

jacoco_plugin

The daemon as an opt-in (--daemon)

On by default since 3.0.

release notes 3.0

JDK 1.5+ to run Gradle; Groovy 1.8.6

Java 17 or newer for the daemon since 9.0; Groovy 4 and Kotlin 2 are embedded; older Java only through toolchains. Reproduced above.

compatibility

sourceCompatibility, IDE jdkName

Project-level sourceCompatibility was removed in 9.0 (reproduced above); toolchains are the preferred form.

toolchains

JCenter and Bintray

jcenter() removed in 9.0, Bintray shut down in 2021. Reproduced above.

upgrading_major_version_9

allprojects { } and subprojects { } to configure all projects

Convention plugins in buildSrc or a build-logic included build; cross-project configuration is incompatible with Isolated Projects (see Convention Plugins & Sharing Build Logic).

sharing_build_logic_between_subprojects

Versions in ext properties

Version catalogs (stable in 7.4; Version Catalogs). Reading an ext value of the root project in a subproject by its bare name is deprecated since 9.6.

version_catalogs

No configuration cache; project used freely at execution time

The configuration cache is stable since 8.1 and the preferred mode since 9.0, and reading Task.project in a task action is deprecated and incompatible with it. Reproduced above.

configuration_cache_requirements

No build cache

Production-ready since 4.0 (Build Cache).

build_cache

Integration tests through hand-made source sets

The JVM Test Suite plugin.

jvm_test_suite_plugin

Plugin tests with ProjectBuilder only

TestKit GradleRunner for end-to-end plugin tests.

test_kit

Gradle 1.x version numbers

Semantic versioning since 9.0.

release notes 9.0.0

The eclipse and idea plugins as the main IDE integration; STS and NetBeans plugins

The IDEs import projects through the Tooling API; the file generation tasks of the plugins are deprecated since 9.6, while the idea { } and eclipse { } model settings that IDEs read remain.

third_party_integration

CloudBees RUN@cloud, Sublime Text, Grunt, Vagrant with Puppet, the 2013 Jenkins pipeline plugins

Discontinued or replaced (not verified individually).

-

Gradle, Maven and Ant

The three tools differ in what "version of the tool" means, and so in what an upgrade is.

Ant Maven Gradle

Pin the tool version in the repository

No wrapper; the Ant on the path is used. Ant 1.10 needs Java 8 or later, and Ant 1.9, which ran on Java 5, has been end of life since 19 June 2024 (Ant: Deprecations & Migration).

The Maven Wrapper: mvn wrapper:wrapper -Dmaven=<version> rewrites .mvn/wrapper/maven-wrapper.properties (Maven Wrapper).

The wrapper task: ./gradlew wrapper --gradle-version <version>, with --gradle-distribution-sha256-sum when the properties file has a checksum.

What the new major needs to run

1.10: Java 8.

Maven 4: Java 17 (Migrating to Maven 4).

Gradle 9: Java 17 for the daemon.

How the old line tells you what breaks

The release notes (WHATSNEW) and the list of deprecated tasks on the page linked above.

The latest 3.x prints warnings about the model; Maven 3.9.16 warns where 3.10.0 fails, and Maven 4 can be made to fail on warnings (see the page on migrating to Maven 4).

--warning-mode=all and the Build Scan deprecations view; --warning-mode=fail for CI.

The tool that migrates the files

-

mvnup (Maven 4).

None for Gradle itself: the wrapper task changes the version, and the guides describe the changes to make by hand.

The Gradle practice that stands out is the deprecation discipline: because the warnings carry the target release and a link, an upgrade can be planned as a task list. Moving a build between the tools is a different problem, covered on Migrating Between Ant, Maven & Gradle, Migrating from Maven and Ant Integration.

Checklist for an upgrade

  1. Commit a green build. Note the Gradle version (./gradlew --version), the Java of the daemon and the plugin versions (./gradlew buildEnvironment).

  2. Move to the last release of the current line (for 8.x, 8.14.6) and read its section in the upgrade guide.

  3. Run ./gradlew <the tasks CI runs> --warning-mode=all --no-configuration-cache, and help --scan when the scan is allowed. Fix every warning, starting with the ones that your own scripts cause; update the plugins that cause the others.

  4. Run again with --warning-mode=fail until it passes. Run with --configuration-cache and fix the problems it reports.

  5. Make sure the daemon will have a Java the target Gradle supports (17 or newer for 9.x), and that toolchains say which Java compiles, tests and runs the code.

  6. Move the wrapper: ./gradlew wrapper --gradle-version <target> --gradle-distribution-sha256-sum <sum>, then ./gradlew wrapper again; check the gradle-wrapper.properties diff and commit the four wrapper files.

  7. Run the build and the tests. Compare the produced archives if you depend on their bytes, and check that every test task still runs tests.

  8. Pin the versions of the quality tools (Checkstyle, PMD, JaCoCo) until the upgrade has settled.

  9. Set org.gradle.warning.mode=fail (or -Dorg.gradle.warning.mode=fail) on CI so that the next deprecation is a failing build before it becomes an error.

  10. Do not migrate to the Kotlin DSL, a version catalog or a new plugin in the same change.

Where to go next