// Copyright (C) 2025 Gradle, Inc.
//
// Licensed under the Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

[[upgrading_version_9]]

= Upgrading within Gradle 9.x.y

This chapter provides the information you need to migrate your Gradle 9.x.y builds to the latest.
For migrating to Gradle 9.0.0, see the <<upgrading_major_version_9.adoc#upgrading_major_version_9, older migration guide>> first.

We recommend the following steps for all users:

. Try running `gradle help --scan` and view the https://docs.gradle.com/develocity/get-started/#identifying_deprecated_gradle_functionality[deprecations view] of the generated Build Scan.
+
image::deprecations.png[Deprecations View of a Gradle Build Scan]
+
This lets you see any deprecation warnings that apply to your build.
+
Alternatively, you can run `gradle help --warning-mode=all` to see the deprecations in the console, though it may not report as much detailed information.
. Update your plugins.
+
Some plugins will break with this new version of Gradle because they use internal APIs that have been removed or changed.
The previous step will help you identify potential problems by issuing deprecation warnings when a plugin tries to use a deprecated part of the API.
+
. Run `gradle :wrapper --gradle-version {gradleVersion}` to update the project to {gradleVersion}.
. Try to run the project and debug any errors using the <<troubleshooting.adoc#troubleshooting, Troubleshooting Guide>>.

[[changes_9.8.0]]
== Upgrading from 9.7.0 and earlier

=== Potential breaking changes

[[kotlin_2_4_10]]
==== Upgrade to Kotlin 2.4.10

The embedded Kotlin has been upgraded from 2.4.0 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.4.10[Kotlin 2.4.10].

[[service_method_added_to_project_settings_gradle_task]]
==== New `service(Class)` method on `Project`, `Settings`, `Gradle`, and `Task`

Gradle 9.8.0 adds an incubating `service(Class)` method to link:{javadocPath}/org/gradle/api/Project.html[`Project`], link:{javadocPath}/org/gradle/api/initialization/Settings.html[`Settings`], link:{javadocPath}/org/gradle/api/invocation/Gradle.html[`Gradle`], and link:{javadocPath}/org/gradle/api/Task.html[`Task`], along with reified `service<T>()` extensions in the Kotlin DSL.
See <<service_injection.adoc#looking_up_services,Looking up services in scripts>> for details.

If your build, plugin, or convention already exposes a member called `service` on one of these types (for example a user-defined extension, a Groovy dynamic property, or a Kotlin extension function), the new built-in member may shadow it or make calls ambiguous.
Rename your member (for example to `myService`) to disambiguate:

[source,kotlin]
.build.gradle.kts
----
// Before: shadowed by the new built-in Project.service(Class)
fun Project.service(name: String): MyService = extensions.getByName(name) as MyService

// After: rename to avoid the conflict
fun Project.myService(name: String): MyService = extensions.getByName(name) as MyService
----

[[task_logging_listeners_are_now_stored_in_the_configuration_cache]]
==== Task logging listeners are now stored in the Configuration Cache

Standard output and error listeners registered on a task's logging manager during configuration (for example via link:{javadocPath}/org/gradle/api/logging/LoggingOutput.html#addStandardOutputListener(org.gradle.api.logging.StandardOutputListener)[`LoggingOutput.addStandardOutputListener`] or link:{javadocPath}/org/gradle/api/logging/LoggingOutput.html#addStandardErrorListener(org.gradle.api.logging.StandardOutputListener)[`LoggingOutput.addStandardErrorListener`]) are now stored in the Configuration Cache and re-registered on a cache hit, so they receive the task's output as they would without the Configuration Cache.

Previously, these listeners were silently discarded and never received output when the Configuration Cache was used.

A listener that cannot be serialized is now reported as a Configuration Cache problem instead of being silently dropped, which may cause a build that previously worked to fail.
If your build registers such a listener and the listener is nonessential, you can opt out and restore the previous behavior — listeners are not serialized, so they are dropped on a cache hit and no problem is reported — by setting:

[source,properties]
----
org.gradle.configuration-cache.unsafe.skip-task-logging-listeners-serialization=true
----

This property is a temporary opt-out and will be removed in a future Gradle release.

[[kotlin_object_singletons_in_configuration_cache]]
==== Kotlin `object` singletons keep their identity in the Configuration Cache

A Kotlin `object` (including `companion object` declarations) stored in the Configuration Cache is now restored as the same singleton instance.
As a consequence, only the singleton's identity is restored, not any mutable state it may hold.

Previously, each `object` was serialized as a regular bean and rebuilt as a _new_ instance on a cache hit, so `deserialized === TheObject` was `false`.
If you relied on mutable state surviving a cache hit, move it into a task property or another bean type that is serialized by value.

=== Deprecations

[[deprecate_implicit_parallel_model_building]]
==== Deprecation of implicit opt-in to parallel model building

Parallel model building lets Tooling API clients (most commonly IDEs during project synchronization) build project models in parallel.
Historically, it had no dedicated control and was enabled implicitly whenever parallel execution (`org.gradle.parallel=true` or `--parallel`) was enabled.

Gradle 9.4.0 introduced the dedicated `org.gradle.tooling.parallel` property to control parallel model building explicitly.
When that property is not set, its value is currently derived from `org.gradle.parallel` to preserve backward compatibility.

Relying on this implicit behavior is now deprecated.
When `org.gradle.parallel` is enabled and `org.gradle.tooling.parallel` is not set explicitly during model building, Gradle emits a deprecation warning.
In Gradle 10, the two properties will become independent, and enabling `org.gradle.parallel` without an explicit value for `org.gradle.tooling.parallel` during model building will be an error.

To avoid the warning and prepare for Gradle 10, set `org.gradle.tooling.parallel` explicitly to either `true` or `false`, for example in `gradle.properties`:

[source,properties]
----
org.gradle.tooling.parallel=true
----

Additionally, the transitional system property `org.gradle.tooling.parallel.ignore-legacy-default` (used by some IDEs during sync to opt out of the legacy default) will no longer be honored in Gradle 10.
Once `org.gradle.parallel` and `org.gradle.tooling.parallel` are independent, the flag is redundant, so Gradle 10 simply ignores it.
No action is required in Gradle 9.x; if you set it explicitly, you can remove it when upgrading to Gradle 10.

[[config_time_external_processes]]
==== Deprecation of starting external processes at configuration time with `ExecOperations` and Java/Groovy standard APIs

Starting an external process at configuration time with link:{javadocPath}/org/gradle/process/ExecOperations.html[`ExecOperations`] or the Java and Groovy standard APIs directly is now deprecated.
This includes:

- `ExecOperations.exec` and `ExecOperations.javaexec`.
- `java.lang.ProcessBuilder.start()` and `ProcessBuilder.startPipeline(...)`.
- `java.lang.Runtime.exec(...)` (all overloads).
- The Groovy process helpers `String.execute(...)`, `String[].execute(...)`, and `List.execute(...)`.

This does *not* affect uses of the link:{javadocPath}/org/gradle/api/provider/ProviderFactory.html#exec(org.gradle.api.Action)[`ProviderFactory.exec`] / link:{javadocPath}/org/gradle/api/provider/ProviderFactory.html#javaexec(org.gradle.api.Action)[`ProviderFactory.javaexec`] providers (`providers.exec` / `providers.javaexec`) or a <<configuration_cache_requirements.adoc#config_cache:requirements:external_processes, `ValueSource`>> that runs a process inside — those remain the supported way to run a process at configuration time.

Starting an external process at configuration time in any of the deprecated ways is already an error when the Configuration Cache is enabled.
To simplify preparation for the Configuration Cache being enabled by default in Gradle 10, and to open the way to it becoming the only mode of execution, Gradle now unconditionally deprecates behaviors that are unsupported with the Configuration Cache.

Migrate to one of the following, in order of preference:

1. *Move the process execution into a task*, so it runs at execution time and benefits from build caching, up-to-date checks, and work avoidance.
Use an link:{javadocPath}/org/gradle/api/tasks/Exec.html[`Exec`] or link:{javadocPath}/org/gradle/api/tasks/JavaExec.html[`JavaExec`] task, or a custom task that injects link:{javadocPath}/org/gradle/process/ExecOperations.html[`ExecOperations`] and runs the process in its task action.
2. For simple cases, use link:{javadocPath}/org/gradle/api/provider/ProviderFactory.html#exec(org.gradle.api.Action)[`providers.exec`] / link:{javadocPath}/org/gradle/api/provider/ProviderFactory.html#javaexec(org.gradle.api.Action)[`providers.javaexec`].
Prefer wiring the returned provider lazily to task inputs, and call `get()` only as a last resort.
3. For more complex output processing, use a <<configuration_cache_requirements.adoc#config_cache:requirements:external_processes, `ValueSource`>> that injects link:{javadocPath}/org/gradle/process/ExecOperations.html[`ExecOperations`] (or uses `java.lang.ProcessBuilder` directly).
The same guidance about wiring the provider lazily to task inputs applies.

This behavior will become an error in Gradle 11.

[[changes_9.7.0]]
== Upgrading from 9.6.0 and earlier

=== Potential breaking changes

[[publish_no_checksums_for_signature_files]]
==== Checksum files are no longer published for signature files

When publishing to Maven or Ivy repositories, Gradle no longer publishes checksum files (such as `.sha1` and `.md5`) for signature files like `.asc` and `.sig`.

A signature is itself an integrity artifact, so a checksum of a signature provides no additional protection, and the standard Maven publishing tooling does not publish these files either.
The signature files themselves continue to be published unchanged; only their checksum files are no longer produced.

[[project_cache_dir_no_longer_watched]]
==== The project cache directory is no longer watched

Gradle no longer watches the _project cache directory_ (the `.gradle` directory in the root project, or a custom location set with `--project-cache-dir`).
Because Gradle is the only writer to this directory, it keeps the virtual file system up to date directly, without relying on OS file system events.

As a result, a custom project cache directory no longer disables file system watching.
Previously, `--project-cache-dir` (or `org.gradle.projectcachedir`) was incompatible with watching: passing `--watch-fs` alongside it failed the build, and leaving watching at its default silently disabled it.
File system watching now follows the usual rules regardless of where the project cache directory is located.

This applies to the project cache directory itself.
Source-dependency check-outs, which live under the project cache directory but whose contents can change externally, remain watched as regular build directories.

See <<file_system_watching.adoc#sec:excluding_files_and_directories,Excluding files and directories>> for details.

[[kotlin_2_4_0]]
==== Upgrade to Kotlin 2.4.0

The embedded Kotlin has been upgraded from 2.3.21 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.4.0[Kotlin 2.4.0].

Starting with Kotlin `2.4.0`, the compiler no longer supports `-language-version=1.9`, which means the K1 compiler is gone entirely.
`2.0` is the new minimum compile target (and is itself deprecated, pointing at `2.2`).

From the `2.4.0` release notes:

- KGP requires a minimum Gradle version of `7.6.3`, fully tested through `9.5.0`.
- Support for Java `26` has been added.

Oldest supported `kotlin-dsl` plugin version is now `6.2.0`, which can be used to build precompiled-script plugins targeting a language level as old as `1.8`. Oldest supported KGP version is `1.9.22` which can be used to write Kotlin plugins targeting language levels as old as `1.4` and be used in Gradle versions as old as `7.2`.

[[kotlin_dsl_impldep_compilation]]
==== Kotlin DSL scripts can no longer reference relocated internal dependencies

Kotlin DSL build scripts (`.gradle.kts`) are now compiled against a prebuilt public API JAR from the Gradle distribution instead of the dynamically generated Gradle API JAR.

As a result, build scripts that import classes from `org.gradle.internal.impldep.*` (Gradle's relocated internal dependencies) will no longer compile.
These classes were never part of Gradle's public API.

If your build scripts reference `org.gradle.internal.impldep.*` types, replace them with the original library coordinates added as dependencies to your build logic:

[source,kotlin]
.build.gradle.kts
----
// Before (will no longer compile):
// import org.gradle.internal.impldep.com.google.gson.Gson

// After: add the dependency to buildscript or buildSrc
buildscript {
    dependencies {
        classpath("com.google.gson:gson:2.11.0")
    }
}
// Then import directly:
// import com.google.gson.Gson
----

NOTE: This change does not affect plugin projects that have `gradleApi()` or `gradleKotlinDsl()` among their dependencies. Those continue to use the full generated Gradle API JAR as before.

[[parameters_none_changes]]
==== Behavior and contract changes for `*Parameters.None`

The marker types used to declare a parameterless action — link:{javadocPath}/org/gradle/workers/WorkParameters.None.html[`WorkParameters.None`], link:{javadocPath}/org/gradle/api/artifacts/transform/TransformParameters.None.html[`TransformParameters.None`], link:{javadocPath}/org/gradle/api/services/BuildServiceParameters.None.html[`BuildServiceParameters.None`], link:{javadocPath}/org/gradle/api/provider/ValueSourceParameters.None.html[`ValueSourceParameters.None`], link:{javadocPath}/org/gradle/api/flow/FlowParameters.None.html[`FlowParameters.None`] and `JvmTestToolchainParameters.None` — have been standardized.

1. **The configuration `Action` is now invoked even when the action declares `None` as its parameters type.**
Calls like `WorkQueue.submit(MyAction.class, p -> ...)`, `FlowActionSpec.parameters { ... }`, `ValueSourceSpec.parameters { ... }`, `BuildServiceSpec.parameters { ... }` and `TransformSpec.parameters { ... }` now always run the configuration block.
When the parameters type is `None`, the block receives the `None` singleton instead of being skipped.
Existing blocks that just configured properties on a real parameters type are unaffected; any code that relied on the block being silently skipped for `None` will now run.

2. **`getParameters()` returns the `None` singleton instead of `null` for parameterless actions.**
Inside a `WorkAction`, `TransformAction`, `BuildService`, `ValueSource` or `FlowAction` that declares `None` as its parameters type, `getParameters()` now returns the `None` singleton.
Previously it could return `null`.
Null-checks against the result are no longer required.

No source change is needed in typical builds.
If you have custom code that branched on `getParameters() == null` to detect a parameterless action, replace it with `getParameters() instanceof WorkParameters.None` (or the equivalent `None` type).

=== Feature previews

[[dependency_resolution_ordering]]
==== Opt into improved dependency resolution ordering

A new feature preview, `ENHANCED_GRAPH_ORDERING`, is available to opt into new dependency resolution ordering behavior that will become the default in Gradle 10.

With this feature preview enabled, Gradle will use a standard breadth-first ordering when using the `DEFAULT` sort order.
Gradle may optionally be configured to return artifacts sorted topologically, using the `CONSUMER_FIRST` sort option, which sorts artifacts earlier than those they depend on.
As usual, the `DEPENDENCY_FIRST` ordering is the reverse of the `CONSUMER_FIRST` ordering.

Notably, unlike the existing sorting methods, all sorting options in Gradle 10 will ignore constraint edges when traversing the resolved graph.

You can enable this feature preview in your `settings.gradle.kts`:

[source,kotlin]
----
// settings.gradle.kts
enableFeaturePreview("ENHANCED_GRAPH_ORDERING")
----

=== Deprecations

[[deprecated_software_model]]
==== Deprecation of the software model

The Gradle software model — including the `model { }` DSL, link:{javadocPath}/org/gradle/model/RuleSource.html[`RuleSource`] plugins, and the native and JVM component types built on top of them — is deprecated and will be removed in Gradle 10.

This affects the following groups of public API types:

- The rule-authoring API in `org.gradle.model`: `RuleSource`, `ModelMap`, `ModelSet`, `ModelElement`, and the `@Model`, `@Mutate`, `@Defaults`, `@Finalize`, `@Validate`, `@Each`, `@Path`, `@RuleInput`, `@RuleTarget`, `@Rules`, `@Managed`, and `@Unmanaged` annotations.
- The component-model base API in `org.gradle.platform.base` and `org.gradle.language.base`: `ComponentSpec`, `BinarySpec`, `LibrarySpec`, `ApplicationSpec`, `LanguageSourceSet`, and related container, builder, and annotation types.
- The native component plugins: `c`, `cpp`, `objective-c`, `objective-cpp`, `assembler`, `windows-resources`, and `native-component`, along with their source-set, binary-spec, build-type, flavor, and prebuilt-library types in `org.gradle.nativeplatform` and `org.gradle.language.*`.
- The native test plugins `cunit`, `cunit-test-suite`, `google-test`, and `google-test-test-suite`, along with the rule-based `test-suite-base` infrastructure in `org.gradle.testing.base`.
- The `componentReport` and `dependentComponents` diagnostic tasks.

Replace software-model-based builds with the current component model:

- For C and {cpp}, use the link:{userManualPath}/cpp_application_plugin.html[`cpp-application`] and link:{userManualPath}/cpp_library_plugin.html[`cpp-library`] plugins.
- For Swift, use the link:{userManualPath}/swift_application_plugin.html[`swift-application`] and link:{userManualPath}/swift_library_plugin.html[`swift-library`] plugins.
- For native testing, use the link:{userManualPath}/cpp_unit_test_plugin.html[`cpp-unit-test`] and link:{userManualPath}/xctest_plugin.html[`xctest`] plugins.

The new plugins configure components through ordinary Gradle extensions (`application { }`, `library { }`, `unitTest { }`) without the `model { }` block, integrate with the configuration cache, and support Gradle's standard dependency-management features.

[[deprecated_unsafe_isolated_projects_properties]]
==== Deprecation of `org.gradle.unsafe.isolated-projects*` property names

The Isolated Projects feature can now be enabled with stable property names that drop the `.unsafe.` segment, alongside a new incubating CLI option `--isolated-projects` / `--no-isolated-projects`:

|===
| Legacy name | New name

| `org.gradle.unsafe.isolated-projects`
| `org.gradle.isolated-projects`

| `org.gradle.unsafe.isolated-projects.diagnostics`
| `org.gradle.isolated-projects.diagnostics`

| `org.gradle.unsafe.isolated-projects.dangerously-ignore-problems`
| `org.gradle.isolated-projects.dangerously-ignore-problems`
|===

The legacy `org.gradle.unsafe.*` names are now deprecated and will be removed in a future release.
They continue to be accepted as aliases for now and currently emit no warning.
Update your `gradle.properties` and command-line invocations to the new names.

When both the legacy and the new name of a property are set — for example, the new name in `gradle.properties` and the legacy name injected by a plugin or CI script on the command line — an explicit `false` from either one disables the feature.
This guarantees that tooling which still opts out via a legacy property name keeps working after a build migrates to the new names.

See the <<isolated_projects.adoc#sec:enable, Enabling Isolated Projects>> section in the Gradle User Manual for the recommended setup.

[[deprecated_custom_collection_types_with_cc]]
==== Deprecation of serializing custom collection and map types with the configuration cache

The configuration cache serializes a fixed set of standard collection, set, map, and queue types (such as `ArrayList`, `LinkedHashSet`, `LinkedHashMap`, and `ArrayDeque`) directly.
A _custom_ subtype of one of these types — for example, `class MyList extends ArrayList<String> {}` — cannot be restored as its own type and is instead restored as the nearest standard type, losing any state or behavior the custom type adds.

Referencing such a custom subtype from a task field or task action is now deprecated and will become an error in Gradle 10.

To fix it, use a standard collection or map type instead of a custom subtype, and store any extra data in separate fields.

See <<configuration_cache_requirements.adoc#config_cache:requirements:custom_collection_types, Custom Collection and Map Types>> for details.

[[deprecated_startparameter_construction]]
==== Deprecation of creating and copying `StartParameter`

Starting in Gradle 10, constructing new instances of link:{javadocPath}/org/gradle/StartParameter.html[`StartParameter`] will no longer be permitted.
It was never intended to be instantiated or copied by build logic, but the public no-argument constructor and the `newInstance()` / `newBuild()` methods made that possible.

The following have been deprecated for removal:

- the public link:{javadocPath}/org/gradle/StartParameter.html[`StartParameter()`] constructor
- link:{javadocPath}/org/gradle/StartParameter.html#newInstance()[`StartParameter.newInstance()`]
- link:{javadocPath}/org/gradle/StartParameter.html#newBuild()[`StartParameter.newBuild()`]

The existing `StartParameter` instance for a build may be obtained via link:{javadocPath}/org/gradle/api/invocation/Gradle.html#getStartParameter()[`Gradle.getStartParameter()`] instead of constructing or copying one.

[[deprecate_signature_as_artifact]]
==== Deprecation of `Signature` as a Configuration artifact

Starting in Gradle 10, link:{javadocPath}/org/gradle/plugins/signing/Signature.html[`Signature`] will no longer implement link:{javadocPath}/org/gradle/api/artifacts/PublishArtifact.html[`PublishArtifact`].
The following methods inherited from `PublishArtifact` have been deprecated and will be removed in Gradle 10:

- `getName()` / `setName(String)`
- `getExtension()` / `setExtension(String)`
- `getType()` / `setType(String)`
- `getClassifier()` / `setClassifier(String)`
- `getDate()` / `setDate(Date)`
- `getBuildDependencies()`
- `builtBy(Object...)`

Furthermore, `Signature` will no longer extend the internal `AbstractPublishArtifact` class or implement the internal `PublishArtifactInternal` interface.
Code that dereferenced these internal types via `Signature` must be updated.
The `shouldBePublished()` method inherited from `PublishArtifactInternal` has also been deprecated for removal.

As a result, a `Signature` will no longer be permitted directly as an artifact of a `Configuration`.
Starting in Gradle 10, the <<signing_plugin.adoc#signing_plugin,Signing Plugin>> will no longer create the `signatures` configuration or automatically add `Signature` instances to it.

The `getConfiguration()` and `setConfiguration(Configuration)` methods on link:{javadocPath}/org/gradle/plugins/signing/SigningExtension.html[`SigningExtension`] have been deprecated and will be removed in Gradle 10.
Creating a configuration named `signatures` before the signing plugin is applied is now also deprecated.

[[deprecate_signature_constructors]]
==== Deprecation of constructing `Signature` instances

Starting in Gradle 10, manually constructing a link:{javadocPath}/org/gradle/plugins/signing/Signature.html[`Signature`] instance will no longer be permitted.
All public constructors for this type have been deprecated for removal.

To sign files, use the `sign` methods on the link:{javadocPath}/org/gradle/plugins/signing/Sign.html[`Sign`] task, or use the `sign` methods on link:{javadocPath}/org/gradle/plugins/signing/SignOperation.html[`SignOperation`].

[[deprecate_sign_operation_signatures]]
==== Deprecation of `getSignatures()` and `getSingleSignature()` on `SignOperation`

The `getSignatures()` and `getSingleSignature()` methods on link:{javadocPath}/org/gradle/plugins/signing/SignOperation.html[`SignOperation`] have been deprecated and will be removed in Gradle 10.

Use `getFilesToSign()` and `getSignatureFiles()` instead.

[[deprecate_sign_classifier]]
==== Deprecation of signing files with a classifier

The following methods that accept a classifier alongside files to sign have been deprecated and will be removed in Gradle 10:

- `Sign.sign(String, File...)`
- `SignOperation.sign(String, File...)`
- `SigningExtension.sign(String, File...)`

The classifier argument is ignored during signing and does not influence the resulting signature file.
Use the `sign(File...)` overloads instead.

[[unsupported_attribute_value_type]]
==== Deprecation of unsupported attribute value types

Attribute value types are limited to: `String`, `Boolean`, any subtype of `Number`, and any type that implements `org.gradle.api.Named`.

Passing any other type to link:{javadocPath}/org/gradle/api/attributes/Attribute.html#of(java.lang.String,java.lang.Class)[`Attribute.of(String, Class)`] — or declaring an unsupported type as the type parameter of an `AttributeCompatibilityRule` or `AttributeDisambiguationRule` — now emits a deprecation warning and will become an error in Gradle 10.

The most common case is a plain Java `Enum` that does not implement `Named`.
Such types often work at first but fail later during dependency resolution, publishing, or configuration cache serialization — where the machinery assumes an attribute value can be reconstructed by name.

To fix a build affected by this deprecation, change the attribute value type to one of the supported types.
For enums, the simplest fix is to make the enum implement `Named` and return the constant's name from `getName()`.

See the <<variant_attributes.adoc#sec:creating-attributes, variant attributes documentation>> for the full set of supported attribute value types.

[[kgp_native_bundle_attribute_enum]]
==== Deprecation of using `KotlinNativeBundleArtifactsTypes` as an attribute value type

The Kotlin Gradle Plugin (KGP) 2.0.x line registers an attribute of type `org.jetbrains.kotlin.gradle.targets.native.toolchain.KotlinNativeBundleArtifactFormat.KotlinNativeBundleArtifactsTypes` — a plain Java `Enum` that does not implement `org.gradle.api.Named`.

This deprecation is a specialized variant of the general <<#unsupported_attribute_value_type, unsupported attribute value type>> deprecation, called out separately so users can identify KGP as the source and remediate by upgrading the plugin.

To fix builds affected by this deprecation, upgrade the Kotlin Gradle Plugin to 2.1.0 or later.
KGP 2.1.0 and newer do not use a plain enum for this attribute and are unaffected.

Like the general case, this deprecation will become an error in Gradle 10.

See the <<variant_attributes.adoc#sec:creating-attributes, variant attributes documentation>> for the full set of supported attribute value types.

[[changes_9.6.0]]
== Upgrading from 9.5.0 and earlier

[[v9_potential_breaking_changes_9_6]]
=== Potential breaking changes

[[agp_8x_incompatible]]
==== Plugins relying on removed Problems API internals are no longer compatible with Gradle 9.6

Several internal classes of the Problems API, such as `org.gradle.api.problems.internal.InternalProblems`, have been removed in Gradle 9.6.
Plugins that bound to these internal classes directly fail at build time with an error mentioning the removed type.

The most common case is Android Gradle Plugin (AGP) 8.x, which relied on these internal classes.
To resolve this, upgrade to AGP 9.x, which only uses the public Gradle API and fully supports Gradle 9.6.
If the failure comes from another plugin, update it to a version that no longer uses Gradle internal APIs, or stay on Gradle 9.5 until such a version is available.

[[concurrency_primitives_prohibited_by_cc]]
==== Concurrency primitives can no longer be serialized by the Configuration Cache

Configuration Cache serialization now reports a clear problem when attempting to serialize most of Java's standard concurrency primitives.
This includes classes and interfaces from the `java.util.concurrent` and `java.util.concurrent.locks` packages, such as `ReentrantLock`, `CountDownLatch`, and `SynchronousQueue`.
Because the Configuration Cache enforces isolation between tasks, these primitives cannot be used correctly for cross-task synchronization.
Allowing serialization would silently give each task its own independent instance (for example, its own lock), so cross-task coordination would never actually occur — that is the bug this error prevents.

Previously, attempts to serialize these types could fail with exceptions such as `java.lang.reflect.InaccessibleObjectException`, without clearly indicating the underlying issue.
If your build requires shared, synchronized state, use <<build_services.adoc#build_services, shared build services>>, which are explicitly designed for safe coordination between tasks.

For a complete list of types that are incompatible with the Configuration Cache, see the <<configuration_cache_requirements.adoc#config_cache:requirements:disallowed_types, documentation>>.

[[v9_upgrade_to_kotlin_2_3_21]]
==== Upgrade to Kotlin 2.3.21

The embedded Kotlin has been upgraded from 2.3.20 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.3.21[Kotlin 2.3.21].

[[v9_upgrade_to_pmd_7_24_0]]
==== Upgrade to PMD 7.24.0

The default version of PMD has been updated from 7.13.0 to link:https://github.com/pmd/pmd/releases/tag/pmd_releases%2F7.24.0[7.24.0].

[[v9_upgrade_to_codenarc_3_7_0]]
==== Upgrade to CodeNarc 3.7.0

The default version of CodeNarc has been updated from 3.6.0 to link:https://github.com/CodeNarc/CodeNarc/blob/v3.7.0/CHANGELOG.md[3.7.0].

[[v9_upgrade_to_ant_1_10_17]]
==== Upgrade to Ant 1.10.17

Ant has been updated to link:https://github.com/apache/ant/blob/rel/1.10.17/WHATSNEW[1.10.17].
Since the previous version was 1.10.15, the link:https://github.com/apache/ant/blob/rel/1.10.16/WHATSNEW[1.10.16] changes are also included.

[[v9_upgrade_to_groovy_4_0_32]]
==== Upgrade to Groovy 4.0.32

Groovy has been updated to link:https://groovy-lang.org/changelogs/changelog-4.0.32.html[4.0.32].
Since the previous version was 4.0.29, the link:https://groovy-lang.org/changelogs/changelog-4.0.30.html[4.0.30] and link:https://groovy-lang.org/changelogs/changelog-4.0.31.html[4.0.31] changes are also included.

[[v9_deprecations_9_6]]
=== Deprecations

[[deprecated_implicit_lookup_in_parent_projects]]
==== Deprecation of implicit lookup of properties and methods in parent projects

In Gradle's Groovy and Kotlin DSLs, when a child project's build script references a property or method that isn't defined locally, the resolution mechanism walks up the parent projects looking for a match:

====
[.multi-language-sample]
=====
.build.gradle (root project)
[source,groovy]
----
ext.foo = "hello"
----
.child/build.gradle
[source,groovy]
----
println(foo) // Resolved through hierarchy — now deprecated
----
=====
[.multi-language-sample]
=====
.build.gradle.kts (root project)
[source,kotlin]
----
extra["foo"] = "hello"
----
.child/build.gradle.kts
[source,kotlin]
----
val foo: String? by project
println(foo) // Resolved through hierarchy — now deprecated
----
=====
====

This implicit inheritance is deprecated and will be removed in Gradle 10.

Implicit hierarchy lookup creates hidden coupling between projects, makes builds harder to reason about (a typo silently resolves to an ancestor's definition instead of failing), and is fundamentally incompatible with <<isolated_projects.adoc#isolated_projects, Isolated Projects>>.

Here is a full list of affected APIs and DSL constructs:

- Dynamic references such as a bare `foo` or `bar()` in Groovy DSL
- Kotlin DSL property delegates such as `val foo: String by project`
- Explicit API calls on the `Project` instance or in a build script:
* `findProperty("foo")`
* `property("foo")`
* `hasProperty("foo")`
* `getProperties()`

To migrate, choose the approach that best fits your use case:

- **Build-wide configuration values** — declare them in `gradle.properties` at the root, and read them in subprojects via `providers.gradleProperty("name")`. They become typed `Provider<String>` values and are available everywhere without any walking.
- **Plugin defaults / shared logic** — extract into a <<sharing_build_logic_between_subprojects.adoc#sharing_build_logic_between_subprojects, convention plugin>> applied to each subproject. Each subproject gets its own configuration; nothing crosses project boundaries.
- **Explicit reference to an ancestor's value** — write `rootProject.ext.foo` (or `parent.ext.foo`) in the child build script. This is a transitional fix: it preserves the cross-project coupling and is therefore not Isolated Projects compatible, but it removes the deprecation warning and makes the dependency explicit.

If you need to keep the original walking semantics, the following helper can be added to a build script or convention plugin:

====
include::sample[dir="snippets/releases/upgrading/findExtraInHierarchy/kotlin",files="child/build.gradle.kts[tags=helper]"]
include::sample[dir="snippets/releases/upgrading/findExtraInHierarchy/groovy",files="child/build.gradle[tags=helper]"]
====

===== Opt into Gradle 10 behavior by disabling implicit lookup in parent projects

Once you have addressed all related deprecations, you can use a feature preview to adopt the Gradle 10 behavior early.
This prevents new accidental implicit lookups from parent projects.

[source,kotlin]
----
// settings.gradle.kts
enableFeaturePreview("NO_IMPLICIT_LOOKUP_IN_PARENT_PROJECTS")
----

This will affect the behavior of APIs and DSL constructs mentioned above.
It will also affect the output of the `properties` task when invoked for subprojects.

[[deprecated_develocity_plugin_pre_4_0]]
==== Deprecation of Develocity plugin versions before 4.0

Earlier versions of the Develocity plugin rely on the implicit property lookup in parent projects, which is <<#deprecated_implicit_lookup_in_parent_projects,deprecated>> and will be removed in Gradle 10.

Upgrade to the link:https://plugins.gradle.org/plugin/com.gradle.develocity[latest Develocity plugin] from the Gradle Plugin Portal.
See the link:https://gradle.com/help/gradle-plugin-compatibility/[Develocity plugin compatibility matrix] for details.

[[deprecated_get_properties]]
==== Deprecation of `getProperties()`

The following usages of `getProperties()` have been deprecated and will be removed in Gradle 10:

- link:{javadocPath}/org/gradle/api/Project.html#getProperties()[`Project.getProperties()`] — calling `getProperties()` or accessing the `properties` property on a `Project` instance.
- Calling `getProperties()` (or accessing the `properties` property) on a build script, settings script, or init script. This is a separate API on the script base class, but has the same problems.

This method, which historically existed for convenience, makes build logic harder to understand and navigate due to the untyped nature of the properties and the fact that they are sourced from many different locations and containers.
Using this method also hurts performance of builds by eagerly resolving properties from the environment, reducing the potential for more frequent Configuration Cache hits.
The hierarchical look-up of properties through the parent projects chain creates implicit coupling of the mutable state between projects.
This impedes the potential of scalability in the future model, where projects are <<sharing_build_logic_between_subprojects.adoc#sec:convention_plugins_vs_cross_configuration,isolated from each other>>.

To access <<build_environment.adoc#sec:project_properties,project properties>>, prefer `providers.gradleProperty("name")` which returns a type-safe `Provider` and is compatible with lazy configuration.
Note that `providers.gradleProperty()` resolves properties at the build level — it does not include properties from `gradle.properties` files in subproject directories, nor extra properties or other properties set dynamically on individual projects.
If you need to access <<writing_build_scripts_intermediate.adoc#sec:extra_properties,extra properties>>, use `ext` properties directly.

The following table shows common migration scenarios for project properties:

[cols="1a,1a", options="header"]
|===
| Before (deprecated) | After (recommended)

| Accessing a single project property:
[source,kotlin]
----
val value = project.properties["myProp"]
----
[source,groovy]
----
def value = project.properties.myProp
----
| Wire the provider as a task property for better Configuration Cache support:
[source,kotlin]
----
val myProp: Provider<String> =
    providers.gradleProperty("myProp")
----
If you need the value immediately:
[source,groovy]
----
def value = providers.gradleProperty("myProp")
    .orNull
----

| Accessing a property with a default value (Kotlin, Java, or Groovy):
[source,kotlin]
----
val value = project.properties
    .getOrDefault("myProp", "default")
----
Accessing a property with a default value (Groovy, idiomatic):
[source,groovy]
----
def value = project.properties["myProp"] ?: "default"
----
| Use `providers.gradleProperty()` with `getOrElse()`:
[source,kotlin]
----
val value = providers
    .gradleProperty("myProp")
    .getOrElse("default")
----
[source,groovy]
----
def value = providers.gradleProperty("myProp")
    .getOrElse("default")
----

| Kotlin property delegation:
[source,kotlin]
----
val myProp: String by project.properties
----
| Get the project property directly:
[source,kotlin]
----
val myProp = project.property("myProp") as String
----

| Forwarding properties as system properties:
[source,kotlin]
----
test {
    systemProperties(
        project.properties.filter {
            it.key.startsWith("test.")
        }
    )
}
----
| Use `providers.gradlePropertiesPrefixedBy()`:
[source,kotlin]
----
test {
    systemProperties(
        providers
            .gradlePropertiesPrefixedBy("test.")
            .get()
    )
}
----

| Token replacement in resource files (common in Minecraft mods, Spring Boot, etc.):
[source,groovy]
----
processResources {
    filesMatching("plugin.yml") {
        expand project.properties
    }
}
----
| Build an explicit map of only the properties you need:
[source,groovy]
----
processResources {
    filesMatching("plugin.yml") {
        expand(
            "version": project.version,
            "name": project.name
        )
    }
}
----
|===

[[kotlin_dsl_delegated_properties]]
==== Deprecation of Kotlin DSL delegated properties

All Kotlin DSL property delegate extensions have been deprecated and will be removed in Gradle 10. This includes container delegates (`by registering`, `by creating`, `by existing`, `by getting`), property delegates (`by project`, `by settings`, `by extra`), and value delegates (`Property<T>` and `ConfigurableFileCollection` getValue/setValue operators).

Use the explicit API instead.

The container delegates (`registering`, `creating`, `existing`, `getting`) are defined on `NamedDomainObjectContainer` and `NamedDomainObjectCollection`. They apply to any container, not just `tasks` or `configurations`.

The examples below use a generic `container` placeholder; replace it with `tasks`, `configurations`, `sourceSets`, or whichever container you use.

**Container delegates:**

[cols="1,1"]
|===
|Deprecated delegate |Replacement

|`val x by container.registering`
|`val x = container.register("x")`

|`val x by container.registering { }`
|`val x = container.register("x") { }`

|`val x by container.registering(Type::class)`
|`val x = container.register<Type>("x")`

|`val x by container.registering(Type::class) { }`
|`val x = container.register<Type>("x") { }`

|`val x by container.creating`
|`val x = container.create("x")`

|`val x by container.creating { }`
|`val x = container.create("x") { }`

|`val x by container.creating(Type::class)`
|`val x = container.create<Type>("x")`

|`val x by container.creating(Type::class) { }`
|`val x = container.create<Type>("x") { }`

|`val x by container.existing`
|`val x = container.named("x")`

|`val x by container.existing { }`
|`val x = container.named("x") { }`

|`val x by container.existing(Type::class)`
|`val x = container.named<Type>("x")`

|`val x by container.existing(Type::class) { }`
|`val x = container.named<Type>("x") { }`

|`val x by container.getting`
|`val x = container.getByName("x")`

|`val x by container.getting { }`
|`val x = container.getByName("x") { }`

|`val x by container.getting(Type::class)`
|`val x = container.getByName<Type>("x")`

|`val x by container.getting(Type::class) { }`
|`val x = container.getByName<Type>("x") { }`

|`val x: Type by container` (lookup)
|`val x = container.getByName("x")`

|`val x: Type by provider` (`NamedDomainObjectProvider<T>` unwrap)
|`val x = provider.get()`
|===

When a container holds exactly one element of a given type, you can look it up by type instead of by name.
This is useful for plugin-contributed tasks whose names are not part of a public contract:

[source,kotlin]
----
// Eagerly resolves the single task of type MyTaskType, regardless of its name.
val myTask = rootProject.tasks.withType<MyTaskType>().single()
----

NOTE: `withType(...).single()` is eager. The task must already exist when this line runs.
For lazy configuration, use `tasks.withType<MyTaskType>().configureEach { ... }` instead.

**Property, extra, and value delegates:**

[cols="1,1"]
|===
|Deprecated delegate |Replacement

|`val p: String by project`
|`val p = project.property("p")`

|`val p: String? by project`
|`val p = project.findProperty("p")`

|`val p: String by settings` (Gradle property)
|`val p = providers.gradleProperty("p").get()`

|`val p: String? by settings` (Gradle property, nullable)
|`val p = providers.gradleProperty("p").orNull`

|`val p: String by settings` (extra property)
|`val p = extra["p"] as String`

|`val p: String? by settings` (extra property, nullable)
|`val p = extra["p"] as String?`

|`val v by extra("value")` or `val v by extra { value }`
|`extra.set("v", value)`

|`val v: String by extra`
|`val v = extra["v"] as String`

|`val v: String? by extra`
|`val v = extra["v"] as String?`

|`val x: String by myProperty` (`Property<T>` read)
|`val x = myProperty.get()`

|`x = "new"` via `var x: String by myProperty` (`Property<T>` write)
|`myProperty.set("new")`

|`val files by myFileCollection` (`ConfigurableFileCollection` read)
|`val files = myFileCollection.getFiles()`

|`files = ...` via `var files by myFileCollection` (`ConfigurableFileCollection` write)
|`myFileCollection.setFrom(...)`

|`val ext: Type by extensions`
|`val ext = extensions.getByType<Type>()`
|===

Delegated properties are being removed because they create silent correctness bugs (e.g. no-op delegates, wrong extra property scope), couple entity names to variable names, diverge between build scripts and class-based plugins, and duplicate functionality already provided by explicit APIs or other DSL sugar.

[[task_dependencies]]
==== Deprecation of accessing task dependency relationships from a task action

Calling the following methods from a task action at execution time is now deprecated and will become an error in Gradle 10:

- link:{javadocPath}/org/gradle/api/Task.html#getTaskDependencies--[`Task.getTaskDependencies()`]
- link:{javadocPath}/org/gradle/api/Task.html#getDependsOn--[`Task.getDependsOn()`]
- link:{javadocPath}/org/gradle/api/Task.html#getMustRunAfter--[`Task.getMustRunAfter()`]
- link:{javadocPath}/org/gradle/api/Task.html#getFinalizedBy--[`Task.getFinalizedBy()`]
- link:{javadocPath}/org/gradle/api/Task.html#getShouldRunAfter--[`Task.getShouldRunAfter()`]

These methods can still be used during configuration time.

The deprecation is only issued if the Configuration Cache is **not** enabled.
When the Configuration Cache is enabled, calls to these methods are reported as Configuration Cache problems instead.
This is another step towards moving users away from idioms that are incompatible with the Configuration Cache, which will become the only mode supported by Gradle in a future release.
For example:

[source,kotlin]
----
// Deprecated: inspecting dependencies from within the task action
tasks.register("report") {
    dependsOn("compileJava")
    doLast {
        taskDependencies.getDependencies(this).forEach { println(it.name) }
    }
}

// Recommended: capture what you need at configuration time
tasks.register("report") {
    dependsOn("compileJava")
    val dependencyNames = dependsOn.filterIsInstance<Task>().map { it.name }
    doLast {
        dependencyNames.forEach { println(it) }
    }
}
----

Please refer to the <<configuration_cache_requirements.adoc#config_cache:requirements:use_project_during_execution, Configuration Cache documentation>> for alternatives that are compatible with the Configuration Cache.

[[task_extensions]]
==== Deprecation of accessing extensions from a task action

Calling link:{javadocPath}/org/gradle/api/Task.html#getExtensions--[`Task.getExtensions()`] from a task action at execution time is now deprecated and will become an error in Gradle 10.

This method can still be used during configuration time.

The deprecation is only issued if the Configuration Cache is **not** enabled.
When the Configuration Cache is enabled, calls to this method are reported as Configuration Cache problems instead.
This is another step towards moving users away from idioms that are incompatible with the Configuration Cache, which will become the only mode supported by Gradle in a future release.

For example:
[source,kotlin]
----
// Deprecated: reading the extension from within the task action
tasks.register("greet") {
    doLast {
        val ext = extensions.getByType<GreetingExtension>()
        println(ext.message.get())
    }
}

// Recommended: capture the value at configuration time
tasks.register("greet") {
    val message = extensions.getByType<GreetingExtension>().message
    doLast {
        println(message.get())
    }
}
----

Please refer to the <<configuration_cache_requirements.adoc#config_cache:requirements:use_project_during_execution, Configuration Cache documentation>> for alternatives that are compatible with the configuration cache.

[[injected_service_types_at_execution]]
==== Deprecation of accessing injected `Project` or `Gradle` services from a task action

Reading an injected service of type link:{javadocPath}/org/gradle/api/Project.html[`Project`] or link:{javadocPath}/org/gradle/api/invocation/Gradle.html[`Gradle`] from a task action at execution time is now deprecated and will become an error in Gradle 10.

These services can still be read during configuration time.

The deprecation is only issued if the Configuration Cache is **not** enabled.
When the Configuration Cache is enabled, such accesses are reported as Configuration Cache problems instead.
This is another step towards moving users away from idioms that are incompatible with the Configuration Cache, which will become the only mode supported by Gradle in a future release.

For example:

[source,kotlin]
----
// Deprecated: reading from the injected Project at execution time
abstract class PrintVersionTask : DefaultTask() {
    @get:Inject
    abstract val project: Project

    @TaskAction
    fun run() {
        println("Building ${project.name} version ${project.version}")
    }
}

// Recommended: declare the values you need as task inputs
abstract class PrintVersionTask : DefaultTask() {
    @get:Input
    abstract val projectName: Property<String>

    @get:Input
    abstract val projectVersion: Property<String>

    @TaskAction
    fun run() {
        println("Building ${projectName.get()} version ${projectVersion.get()}")
    }
}

tasks.register<PrintVersionTask>("printVersion") {
    projectName.set(project.name)
    projectVersion.set(project.version.toString())
}
----
Please refer to the <<configuration_cache_requirements.adoc#config_cache:requirements:use_project_during_execution, Configuration Cache documentation>> for alternatives that are compatible with the Configuration Cache.

[[task_in_task_dependency_closure]]
==== Deprecation of accessing `Task` in `dependsOn` closures

Using the `Task` argument provided to closures passed to the link:{javadocPath}/org/gradle/api/Task.html#dependsOn(java.lang.Object...)[`Task.dependsOn`] method has been deprecated.
Starting in Gradle 10, closures passed to `Task.dependsOn` will no longer receive a `Task` argument.

The following code will not be permitted starting in Gradle 10:

[source,groovy]
----
def foo = tasks.register("foo")

// The below usages of dependsOn access the Task object provided as a parameter.
// This is deprecated behavior.
tasks.register("bar") {
    dependsOn { task ->
        task.getName()
        foo
    }
}
tasks.register("baz") {
    dependsOn {
        it.getName()
        foo
    }
}

// Build logic may continue to pass closures to dependsOn without accessing the provided Task.
tasks.register("okay") {
    dependsOn {
        foo
    }
}
----

[[undeclared_artifact_transform_input]]
==== Deprecation of undeclared Artifact Transform execution by tasks
Invoking an Artifact Transform from a task that does not declare the resolution as a task input is now deprecated and will become an error in Gradle 10.

With the Configuration Cache, if an Artifact Transform is not properly declared as an input, and requires a producer project that no other scheduled task requires, the producer may not be scheduled and may be unreachable.
This can manifest as `project <name> not found` errors.

Now, a deprecation is emitted for each task that executes an Artifact Transform that is not declared as an input.
This provides an early warning for build scenarios that were only working "by accident".

To fix this warning, declare the resolution triggering the Artifact Transform as a task input.
You can do this by exposing a configurations' `files` collection through a typed `@InputFiles` property on a custom task type.
If you must use an ad-hoc task, supply `myCustomConfiguration.files` to  `inputs.files(...)`, or, when consuming an `ArtifactCollection`, supply `ArtifactCollection.getArtifactFiles()`.
In the common case, both of these methods wire the transform steps into the execution plan so the task is recognized as a declarer of the transform.

Declaring a transformed `FileCollection` view as a task input:

[source,kotlin]
----
val view = configurations["implementation"].incoming.artifactView {
    attributes.attribute(color, "green")
}.files

// Deprecated: the doLast reads view.files without declaring view as a task input.
tasks.register("consume") {
    doLast {
        println("result = ${view.files.map { it.name }}")
    }
}

// Recommended: expose the FileCollection through a typed @InputFiles property on a custom task type.
abstract class Consume : DefaultTask() {
    @get:InputFiles
    abstract val colored: ConfigurableFileCollection

    @TaskAction
    fun run() {
        println("result = ${colored.files.map { it.name }}")
    }
}

tasks.register<Consume>("consume") {
    colored.from(view)
}
----

Declaring a transformed `ArtifactCollection` view as a task input — note that declaring the raw `Configuration` (`inputs.files(configurations["compile"])`) is NOT enough, because it only schedules the source artifacts, not the transforms:

[source,kotlin]
----
val artifacts = configurations["compile"].incoming.artifactView {
    attributes { attribute(usage, "transformed") }
}.artifacts

// Deprecated: declaring the raw configuration doesn't wire the transform.
tasks.register("show") {
    inputs.files(configurations["compile"])
    doLast {
        println("files: ${artifacts.map { it.file.name }}")
    }
}

// Recommended: declare the transformed artifact view's artifactFiles as the input.
tasks.register("show") {
    inputs.files(artifacts.artifactFiles)
    doLast {
        println("files: ${artifacts.map { it.file.name }}")
    }
}
----

[NOTE]
====
**How declaration is detected**

Gradle determines whether a task has declared a transform output as an input by walking the task's transitive dependency subgraph in the execution plan after the work graph is fully built. Any transform step reachable from the task through dependency edges, without crossing into another task, is considered declared by that task. This covers chained transforms, `@InputArtifactDependencies` feeds across components, and `componentFilter`-restricted artifact views uniformly.

If you write `inputs.files(view)`, `inputs.files(artifacts.artifactFiles)`, or expose the collection through a typed `@InputFiles` property, Gradle's input wiring places the corresponding transform nodes into the task's dependency successors, and the post-graph walk will recognise the task as a declarer. Declaring through `dependsOn(taskA)` on another task that itself declares the input does NOT count: only direct declaration by the task that performs the query counts.
====

[[dependency_project_notation]]
==== Deprecation of using Project objects as dependency notation

Passing a `Project` object directly as a dependency notation has been deprecated and will become an error in Gradle 10.

Previously, you could declare a project dependency by passing a `Project` instance directly:

====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
def someProject = project(":some-project")
dependencies {
    implementation(someProject)  // Deprecated
}
----
=====
====

Instead, use the `project()` method on `DependencyHandler` or the `createProjectDependency()` method on `DependencyFactory`:

====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
dependencies {
    implementation(project(":some-project"))
}
----
=====
====

Or, when using the `DependencyFactory` directly (e.g., from a plugin):

[source,java]
----
Configuration implementation = project.getConfigurations().getByName("implementation");
ProjectDependency dependency = project.getDependencyFactory().createProjectDependency(":some-project");
implementation.getDependencies().add(dependency);
----

[[deprecated_maven_artifact_urls]]
==== Deprecation of `artifactUrls` on Maven repositories

The `artifactUrls` family of methods on link:{javadocPath}/org/gradle/api/artifacts/repositories/MavenArtifactRepository.html[`MavenArtifactRepository`] is deprecated and will be removed in Gradle 10.

This includes:

- `MavenArtifactRepository.getArtifactUrls()`
- `MavenArtifactRepository.artifactUrls(Object...)`
- `MavenArtifactRepository.setArtifactUrls(Set)`
- `MavenArtifactRepository.setArtifactUrls(Iterable)`

The same deprecation applies to passing `artifactUrls` as a key in the map argument to `RepositoryHandler.mavenCentral(Map)`, since that path delegates to the methods above.

These methods let a single Maven repository declaration look for POMs at the base URL while looking for artifacts (such as JARs) at one or more additional URLs.
This is a Gradle-specific extension with no equivalent in Maven, and there is no direct replacement.

[[deprecated_repository_handler_map_overloads]]
==== Deprecation of `RepositoryHandler.flatDir(Map)` and `RepositoryHandler.mavenCentral(Map)`

The map-argument overloads `RepositoryHandler.flatDir(Map)` and `RepositoryHandler.mavenCentral(Map)` are deprecated and will be removed in Gradle 10.
Use the action-based overloads (`flatDir(Action)`, `mavenCentral(Action)`) instead — they are typed, work in both the Groovy and Kotlin DSLs, and are the canonical way to configure a repository.

For `flatDir`:

====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
repositories {
    flatDir {
        name = "libs"
        dirs "libs1", "libs2"
    }
}
----
=====
[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
repositories {
    flatDir {
        name = "libs"
        dirs("libs1", "libs2")
    }
}
----
=====
====

For `mavenCentral`:

====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
repositories {
    mavenCentral {
        name = "nonDefaultName"
    }
}
----
=====
[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
repositories {
    mavenCentral {
        name = "nonDefaultName"
    }
}
----
=====
====

[[deprecate_build_needed_build_dependents_tasks]]
==== Deprecation of `buildNeeded` and `buildDependents` tasks

The `buildNeeded` and `buildDependents` tasks from the Java plugins are deprecated and will be removed in Gradle 10.

`buildNeeded` builds the current project along with all projects it depends on.
`buildDependents` does the opposite: it builds the current project and all projects that depend on it.
These tasks were introduced back in Gradle 0.8.
Their implementation is incompatible with Configure on Demand and Isolated Projects, and the tasks themselves are too rigid to cover the range of use cases users actually need.
Existing Gradle functionality covers these use cases, so rather than rework the tasks, we are removing them.

`buildNeeded` depends on the current project's `build` task and on the `buildNeeded` task of each project that appears as a direct dependency in the `testRuntimeClasspath` configuration.
Because those `buildNeeded` tasks depend on each other, the result is that the entire project dependency closure is built.

Depending on what you were using `buildNeeded` for, one of the following may replace it:

- If you only need to build or test a specific project, run that task directly (e.g., `./gradlew :proj:build`) and let Gradle resolve its task dependencies automatically.
- For running tests across all projects the current project depends on, use <<test_report_aggregation_plugin.adoc#test_report_aggregation_plugin, test report aggregation>>.
- For plugin authors and advanced build logic, use <<artifact_views.adoc#artifact-views, Artifact Views>> to select artifacts from projects in a dependency graph and wire their producer tasks as dependencies.

There is no direct replacement for `buildDependents`.
For most workflows, running all tasks with a given name (e.g., `./gradlew test`) combined with up-to-date checks and build caching will skip work for unaffected projects, at the cost of configuring all projects.

[[deprecate_getTaskDependencyFromProjectDependency]]
==== Deprecation of `Configuration.getTaskDependencyFromProjectDependency()`

The link:{javadocPath}/org/gradle/api/artifacts/Configuration.html#getTaskDependencyFromProjectDependency(boolean,java.lang.String)[`Configuration.getTaskDependencyFromProjectDependency(boolean, String)`] method has been deprecated and will be removed in Gradle 10.

It exists to support the `buildNeeded` and `buildDependents` tasks deprecated above, and has no other intended use.
Its implementation is fundamentally incompatible with Isolated Projects and Configure on Demand.

There is no direct replacement for this method.
If you were using it to reach tasks across project boundaries, <<artifact_views.adoc#artifact-views, Artifact Views>> let you select artifacts from dependent projects and let Gradle wire the producer tasks through normal dependency resolution.

[[ide_task_deprecation]]
==== Deprecation of file generation tasks of IDE plugins

The <<idea_plugin.adoc#idea_plugin,`idea`>> and <<eclipse_plugin.adoc#eclipse_plugin,`eclipse`>> plugins contribute tasks for generating IDE specific files on disk.
These tasks were originally the primary way to set up a project for an IDE.
However, all modern IDEs now have built-in Gradle integration (IntelliJ IDEA's Gradle import, Eclipse Buildship, etc.) and can import Gradle projects directly without needing these generated files.

We are deprecating these file generation tasks:

* `idea` / `ideaProject` / `ideaModule` / `ideaWorkspace` (generate `.ipr`, `.iml`, `.iws` files)
* `eclipse` / `eclipseProject` / `eclipseClasspath` / `eclipseJdt` / `eclipseWtpComponent` / `eclipseWtpFacet` (generate `.project`, `.classpath`, `.settings/` files)
* `openIdea` (generates files and opens IDEA)
* associated `clean*` tasks

In addition to the tasks, certain model properties that only affect the generated files are also deprecated.
These include `idea.module.iml { ... }`, `idea.project.ipr { ... }`, `idea.workspace { ... }`, `idea.targetVersion`, `idea.pathVariables`, `idea.module.pathVariables`, `idea.project.projectLibraries`, `eclipse.wtp.facet { ... }`, the `file { ... }` merging hooks on `EclipseJdt` and `eclipse.wtp.component`, and the `withXml { ... }` hook on all `file { ... }` blocks.
The `beforeMerged { ... }` and `whenMerged { ... }` hooks on the eclipse project and classpath models are _not_ deprecated, as they also affect the model consumed by the IDE.

The rest of `eclipse.wtp { ... }` is _not_ deprecated: the `eclipse.wtp.component { ... }` configuration feeds the WTP classpath attributes surfaced via the Tooling API, which Eclipse Buildship consumes.

The model properties that affect how IDEs understand your project (source directories, language levels, dependency scopes, etc.) are _not_ deprecated.
When you apply the plugins and configure the `idea { ... }` or `eclipse { ... }` blocks in your build scripts, you can customize how your IDE understands your project.
These customizations are picked up automatically by the Gradle integration built into IDEs.
This functionality will continue to work unchanged.

NOTE: Not all the functionality of the plugins is being deprecated, *only* the tasks and some model properties strictly related to them.

[[deprecated_pmd_target_jdk]]
==== Deprecation of `targetJdk` on the PMD plugin

The `targetJdk` property on link:{javadocPath}/org/gradle/api/plugins/quality/Pmd.html[`Pmd`] and link:{javadocPath}/org/gradle/api/plugins/quality/PmdExtension.html[`PmdExtension`], along with the link:{javadocPath}/org/gradle/api/plugins/quality/TargetJdk.html[`TargetJdk`] enum, are deprecated and will be removed in Gradle 10.

This includes:

- `Pmd.getTargetJdk()` / `Pmd.setTargetJdk(TargetJdk)`
- `PmdExtension.getTargetJdk()` / `PmdExtension.setTargetJdk(TargetJdk)` / `PmdExtension.setTargetJdk(Object)`
- The `TargetJdk` enum and its `toVersion(Object)` static method
- `PmdPlugin.getDefaultTargetJdk(JavaVersion)`

These were used by PMD versions older than 5.0 to select the target Java language level via Ant's `targetjdk` attribute.
PMD 5.0 and later infer the language version from the configured rule sets, so this property has been a no-op for all supported PMD versions.  Gradle does not support PMD versions earlier than PMD 5.1.0.

There is no replacement.
Remove any `targetJdk` configuration from your build.

[[deprecate_problem_spec_severity]]
==== Deprecation of `ProblemSpec.severity()`

Setting problem severity explicitly when creating a new instance is now deprecated.
Severity is instead determined by the reporting method: `ProblemReporter.report()` produces warnings and `ProblemReporter.throwing()` produces errors.
Calling `.severity()` on `ProblemSpec` is now a no-op and will be removed in Gradle 10.

[[changes_9.5.0]]
== Upgrading from 9.4.0 and earlier

[[v9_potential_breaking_changes_9_5]]
=== Potential breaking changes

[[v9_upgrade_to_kotlin_2_3_20]]
==== Upgrade to Kotlin 2.3.20

The embedded Kotlin has been upgraded from 2.3.10 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.3.20[Kotlin 2.3.20].

[[v9_plugins_requested_by_precompiled_settings_plugins_validated]]
==== Plugins requested by precompiled settings plugins are now validated at compile time

Gradle now validates plugin requests in precompiled settings plugins (`*.settings.gradle.kts`) during compilation, failing the build if a requested plugin cannot be resolved.
This matches the existing behavior for precompiled script plugins targeting `Project`.

Previously, if a precompiled settings plugin was never applied, invalid plugin requests in its `plugins {}` block went undetected until the plugin was used by a consuming build, at which point the build would fail unexpectedly.

[source,kotlin]
.buildSrc/src/main/kotlin/my-precompiled-plugin.settings.gradle.kts
----
plugins {
    id("com.example.invalid.plugin") // now fails at compile time if unresolvable
}
----

If you have precompiled settings plugins that are declared but not currently applied, check their `plugins {}` blocks and remove or fix any plugin requests that cannot be resolved from the dependencies available in the build being compiled.

[[v9_windows_start_script_reworked]]
==== The Windows start script has been reworked to improve usability

The default Windows start script template (used for `gradlew.bat` and application start scripts) has been reworked to improve usability and consistency with the Unix shell script.

If you use a custom start script template or invoke `gradlew.bat` from another batch file, review the changes below, as some may affect your build.

The new script includes the following changes:

- The `OS` variable check has been removed, since Gradle no longer supports non-NT-based versions of Windows.
- `endlocal` is now called before invoking the application, so environment variable changes made inside the script do not leak into the invoked process. Any environment variables needed by the application must be set before calling `gradlew.bat`.
- `& CALL` is now used after invoking the application, which suppresses the "Terminate batch job (Y/N)?" prompt. If your scripts rely on this prompt for flow control, they will need to be updated.
- `exit /b` has been replaced with `"%COMSPEC%" /c exit`, enabling the use of `&&` and `||` operators when calling the script from another batch file.

Upgrading the wrapper to the new script may cause a one-time error if done before Gradle 8.14, because `cmd.exe` re-reads the script after it changes.
To avoid this, first upgrade to Gradle 8.14 or 9.0.0, then upgrade to 9.5.0.

[[v9_values_tracked_as_configuration_cache_input]]
==== Calling `.values()` on environment variables or system properties is now tracked as Configuration Cache input

Starting with Gradle 9.5.0, calling `.values()` on the maps returned from `System.getenv()` or `System.getProperties()` now records all environment variables or system properties as Configuration Cache inputs.
This is consistent with how <<configuration_cache_requirements.adoc#config_cache:requirements:reading_sys_props_and_env_vars, `.forEach()` calls are already tracked>>.

As a result, if your build configuration uses `System.getenv().values()` or `System.getProperties().values()`, any change to any environment variable or system property will invalidate the Configuration Cache entry, not just the ones your build actually uses.
If you experience unexpected cache invalidations after upgrading, check whether your build or any plugins call `.values()` on these maps, and consider switching to targeted property lookups instead.

[[v9_dependency_lockfiles_platform_independent_line_ending]]
==== Dependency lockfiles are generated with a platform-independent line ending

In the context of <<dependency_locking#locking-versions, Dependency Locking>>, lockfiles now use Unix line endings (LF, `\n`) instead of the system default.
Existing lockfiles using Windows line endings (CRLF, `\r\n`) can still be read, but newly generated lockfiles will always use LF.

When running a lock update with Gradle 9.5.0 or above, if your lockfiles were previously generated on Windows, all line endings will be updated to LF. This will appear as a large diff in version control even if no dependencies have changed. This is expected and only happens once.

[[v9_dependency_verification_armored_key_rings_non_ascii]]
==== Dependency verification armored key rings now render non-ASCII characters correctly

In the context of <<dependency_verification#verifying-dependencies, Dependency Verification>>, when a key has metadata that contains non-ASCII characters, it is now properly rendered in the key header.

When running a <<dependency_verification#sec:local-keyring,dependency verification key export>> with Gradle 9.5.0 or above, if your keyring already contained keys with non-ASCII metadata, the exported armored keyring will be updated to render those characters correctly.
This is expected to happen only once.

[[v9_outgoing_variants_hide_attributeless_by_default]]
==== The `outgoingVariants` and `resolvableConfigurations` reports now hide variants without attributes by default

The `outgoingVariants` and `resolvableConfigurations` report tasks now only show variants and configurations that have attributes defined, since only these can participate in variant-aware dependency resolution.

Previously, variants and configurations without attributes were included in the default output. They are now hidden unless the `--all` flag is passed.

When shown (via `--all`), attributeless entries are marked with `(n)` in the report to indicate they are not selectable via variant-aware resolution.

If you rely on seeing attributeless variants or configurations in these reports, add `--all` to your invocation:

[source,bash]
----
./gradlew outgoingVariants --all
./gradlew resolvableConfigurations --all
----

[[v9_deprecations_9_5]]
=== Deprecations

[[deprecate_exit_environment_var]]
==== Deprecation of setting an exit environment variable

link:{javadocPath}/org/gradle/jvm/application/tasks/CreateStartScripts.html#getExitEnvironmentVar()[`CreateStartScripts.getExitEnvironmentVar()`] and link:{javadocPath}/org/gradle/jvm/application/tasks/CreateStartScripts.html#setExitEnvironmentVar(java.lang.String)[`CreateStartScripts.setExitEnvironmentVar(String)`] have been deprecated and will be removed in Gradle 10.0.0.

As of this release, these methods are no-ops and have no effect, even with custom start script templates.
This is a result of the link:https://github.com/gradle/gradle/pull/36510[Windows start script template no longer using an exit environment variable].

If you are using a custom start script template that references the exit environment variable, update your template to remove this dependency.
There is no replacement API, the exit environment variable concept has been removed entirely.

[[changes_9.4.0]]
== Upgrading from 9.3.1 and earlier

[[v9_potential_breaking_changes_9_4]]
=== Potential breaking changes

[[v9_project_builder_enforces_consistent_build_scoped_locations]]
==== `ProjectBuilder` now enforces consistent build-scoped locations

link:{javadocPath}/org/gradle/testfixtures/ProjectBuilder.html[`ProjectBuilder`] allows you to configure a specific project directory for tests.
While `project.projectDir` and `project.rootDir` have always respected this setting, `project.layout.settingsDirectory` previously did not.
This discrepancy could cause file resolution to inadvertently escape the project directory.

Gradle now anchors the settings search directly to the configured project directory.
This ensures that `layout.settingsDirectory`, `projectDir`, and `rootDir` all point to the same consistent location.
Projects created via `ProjectBuilder` are now better isolated from the host build environment.

If your tests specifically relied on `layout.settingsDirectory` pointing to an external location, they will need to be adjusted.
Even if you do not use `settingsDirectory` directly, you may still observe changes in file resolution.
Previously, dependency management files could be "leaked" into the test from the host build environment; this is no longer the case.

For more details see link:https://github.com/gradle/gradle/issues/32928[the related issue].

[[v9_java_gradle_plugin_compile_only_api]]
==== The `java-gradle-plugin` plugin now adds the `gradleApi()` dependency to the `compileOnlyApi` scope

The `gradleApi()` dependency is now added to the `compileOnlyApi` scope instead of the `api` scope.
This prevents the `gradleApi()` from leaking into the runtime classpath of other dependents of the plugin project.

This might break projects that were implicitly relying on the `gradleApi()` being on the compilation or runtime classpath.
If this is the case, add a `gradleApi()` dependency to the appropriate scope to restore the previous behavior.

For most plugin projects, this change should be transparent as:

- at compilation time, the plugin project will get the `gradleApi()`
- the default test source set will also automatically get the `gradleApi()` dependency on the compilation and runtime classpaths

If any additional test source set is used (e.g., integration tests), the `plugin` extension offers the `plugins.testSourceSet` method to register the source set for automatic management.
If this cannot be done, a regular declaration of the `gradleApi()` dependency on the test source can be used as well.

[[v9_stricter_validation_for_published_plugins]]
==== Stricter validation for published plugins

For plugin builds that apply any of the `com.gradle.plugin-publish`, `ivy-publish`, or `maven-publish` plugins, Gradle now automatically enables stricter validation of plugin code.

In order not to break your builds, this does not apply to local plugins (in `buildSrc` or included builds containing build logic).
However, we encourage you to always enable stricter validation:

.build.gradle.kts
[source,kotlin]
----
tasks.validatePlugins {
    enableStricterValidation = true
}
----

[[v9_codenarc_compilation_classpath_set_by_default]]
==== CodeNarc compilation classpath is set by default

The CodeNarc plugin now automatically populates the `compilationClasspath` of a `CodeNarc` task with the compile classpath of its associated source set.

CodeNarc offers link:https://codenarc.org/codenarc-enhanced-classpath-rules.html[Enhanced Classpath Rules] (like `UnusedImport` or `DuplicateImport`) that require the project's compiled classes and dependencies to be analyzed for full accuracy.
Previously, you had to wire this up manually.
Now, it works out of the box.

This change introduces a task dependency.
The `CodeNarc` task must now wait for the `compile` task to finish so it can access the compiled classes.

If you do not use enhanced rules and want to restore parallel execution, you can manually empty the `compilationClasspath`:

.build.gradle.kts
[source,kotlin]
----
plugins {
    id("groovy")
    id("codenarc")
}

tasks.withType<CodeNarc>().configureEach {
    // Override the default compilation classpath
    compilationClasspath = files()
}
----

If your build relies on a custom configuration for the `compilationClasspath` of a `CodeNarc` task, you will need to continue explicitly setting it to override the new default behavior.

[[v9_system_property_priority_for_wrapper_execution]]
==== System property priority for Wrapper execution has changed

The priority of <<build_environment.adoc#sec:gradle_system_properties,system properties>> passed to the <<gradle_wrapper.adoc#gradle_wrapper,Gradle Wrapper>> now correctly follows the documented order of precedence.
In previous versions, properties defined in `gradle.properties` files could unexpectedly override those passed via the command line.

If a property is defined in multiple locations, Gradle now strictly honors the following hierarchy (from highest to lowest):

[cols="1,2,3", options="header"]
|===
| Priority | Source | Example
| 1 (Highest) | <<command_line_interface.adoc#command_line_interface,Command Line Option>> | `./gradlew build -Dproperty.name=value`
| 2 | <<directory_layout.adoc#dir:gradle_user_home,Gradle User Home>> | `~/.gradle/gradle.properties`
| 3 (Lowest) | <<directory_layout.adoc#dir:project_root,Project Directory>> | `[project-root]/gradle.properties`
|===

For more details see the link:https://github.com/gradle/gradle/issues/36189[related issue].

[[v9_upgrade_to_kotlin_2_3_0]]
==== Upgrade to Kotlin 2.3.0

The embedded Kotlin has been upgraded from 2.2.21 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.3.0[Kotlin 2.3.0].

[[v9_upgrade_to_zinc_1_12_0]]
==== Upgrade to Zinc 1.12.0

Zinc has been updated to https://github.com/sbt/zinc/releases/tag/v1.12.0[1.12.0].

[[v9_deprecations_9_4]]
=== Deprecations

[[findAll_removal]]
==== Deprecation of `DomainObjectCollection.findAll(Closure)`

The `findAll(Closure)` method on Gradle collections is now deprecated and scheduled for removal in Gradle 10.0.0.

This method relies specifically on Groovy types and eagerly evaluates the contents of the container.

To fix this, use the similar `DomainObjectCollection.matching(Spec)`.
While not a direct replacement for `findAll`, `matching` is lazy, it returns a new collection that only filters elements as they are actually needed by the build:

[source,groovy]
----
// Deprecated:
def checkTasks = tasks.findAll { it.name.startsWith("check") }

// Recommended:
def checkTasks = tasks.matching { it.name.startsWith("check") }
----

[[deprecated_test_methods]]
==== Deprecation of methods taking `Closure` on `Test` tasks

The following APIs are deprecated and will be removed in Gradle 10.0.0:

- `AbstractTestTask.onOutput(Closure)` can be replaced with `AbstractTestTask.addTestOutputListener(TestOutputListener)`
- `AbstractTestTask.beforeTest(Closure)` can be replaced with `AbstractTestTask.addTestListener(TestListener)`
- `AbstractTestTask.afterTest(Closure)` can be replaced with `AbstractTestTask.addTestListener(TestListener)`
- `AbstractTestTask.beforeSuite(Closure)` can be replaced with `AbstractTestTask.addTestListener(TestListener)`
- `AbstractTestTask.beforeSuite(Closure)` can be replaced with `AbstractTestTask.addTestListener(TestListener)`
- `Test.testFramework(Closure)` can be replaced with `Test.options(Action)`

[[deprecate_apply_false_in_precompiled_script_plugins]]
==== Deprecation of `apply false` in precompiled script plugins

The use of `apply false` within <<implementing_gradle_plugins_precompiled.adoc#implementing_precompiled_plugins, precompiled script plugins>> is now deprecated and will result in an error in Gradle 10.0.0.

In a precompiled script, the `plugins {}` block behaves differently than in a standard build script.
Currently, if you write `apply false`, Gradle applies the plugin anyway.
This creates confusion because the syntax suggests you are merely adding a plugin to the classpath without activating it, which is not what actually happens.

The fix depends on whether you actually want the plugin to be active in your precompiled script:

- If you want to use the plugin: Remove the `apply false` statement.
- If you do NOT want to use the plugin: Delete the line entirely.

.my-plugin.gradle.kts
[source,kotlin]
----
plugins {
    // Deprecated (and misleading, as it is still applied)
    id("org.gradle.test-retry") apply false
    // The plugin will still be on the classpath,
    // but it will not be applied as part of this precompiled script plugin.

    // Recommended: Either remove 'apply false' to keep using it,
    // or delete the line to stop using it.
    id("org.gradle.test-retry")
}
----

[[deprecate_version_in_precompiled_settings_script_plugins]]
==== Deprecation of `version` in precompiled Settings script plugins

The use of `version` within <<implementing_gradle_plugins_precompiled.adoc#settings_pre_compiled_plugins, precompiled Settings script plugins>> is now deprecated and will become an error in Gradle 10.0.0.

In precompiled scripts, `version` has no effect.
The plugin version is already fixed by the script's own build file; declaring it again inside the script is ignored and causes confusion about which version is actually in use.

To fix this, remove the `.version()` or `version "..."` call from the `plugins {}` block:

.my-plugin.settings.gradle.kts
[source,kotlin]
----
plugins {
    // Deprecated:
    id("org.gradle.test-retry") version "x.y.z"
    // Recommended:
    id("org.gradle.test-retry")
}
----

[[v9_deprecation_of_dependencies_get_project]]
==== Deprecation of `Dependencies.getProject()` method

The `getProject()` method on `Dependencies` has been deprecated and will be removed in Gradle 10.0.0.

`Dependencies` is used to configure dependencies in test suites.
Previously, `getProject()` was used by Gradle internally to reference the current project.
While never intended for public use, it could be used unintentionally in build scripts.

For example, the following configuration accidentally invokes the deprecated method because `project.path` resolves against the `Dependencies` object rather than the top-level `Project` object:

[source,kotlin]
----
testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) {
            dependencies {
                implementation(project(project.path)) // `project.path` accesses `Dependencies.getProject()`
            }
        }
    }
}
----

The configuration above may now fail to compile with an error in 9.5.0 if `-Werror` is set.

To depend on the current project, use the `project()` method without arguments.
This syntax is the idiomatic way to reference the project in both test suites and standard `dependencies {}` blocks:

[source,kotlin]
----
testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) {
            dependencies {
                implementation(project())
            }
        }
    }
}
----

[[changes_9.3.0]]
== Upgrading from 9.2.0 and earlier

[[v9_potential_breaking_changes_9_3]]
=== Potential breaking changes

[[v9_referential_equality_not_guaranteed_for_project]]
==== Referential equality is not guaranteed for `Project` instances

Instances of `Project` type representing the same logical Gradle project do not provide any guarantee of being equal by reference (`==` in Java, `===` in Kotlin).
However, historically it was possible to observe that such `Project` instances were exactly the same.

In Gradle 9.3.0, `Project` instances (for the same logical project) **can be different** with respect to the referential equality,
even if obtained within the same context, e.g., in the same build script.
This change is necessary to facilitate future performance improvements of Gradle.
The `Project.equals()` equality behavior remains unchanged.

Avoid referential equality in Kotlin:

.build.gradle.kts
[source,kotlin]
----
// DON'T do this
project.rootProject === project.parent
----

Avoid referential equality in Groovy:

.build.gradle
[source,groovy]
----
// DON'T do this
project.rootProject.is(project.parent)
----

Avoid referential equality in Java:

MyPlugin.java
[source,java]
----
// DON'T do this
project.getRootProject() == project.getParent();
----

In general, it is better to check project equality via `Project.getPath()` or `Project.getBuildTreePath()` for composite-build support.
The paths are also better suited to be keys in data structures, like maps.

[[v9_testng_output_may_change]]
==== TestNG output may change when using versions before 6.9.13.3

As part of the `AbstractTestTask` refactoring, Gradle’s integration with TestNG has been updated.
Gradle now relies on a correctly functioning `IClassListener` to report the hierarchy of test classes and methods.

When using TestNG versions **earlier than 6.9.13.3**, this can lead to different or degraded output:

- With older versions, class information may be lost. For example, output that previously looked like: `org.gradle > TestClass > ok` may now be reported simply as: `ok`.
- For TestNG versions **from 6.9.10 up to (but not including) 6.9.13.3**, the `IClassListener` API exists but is broken. This can result in even worse output, such as empty or missing names.

To get correct and stable output, we recommend upgrading to **TestNG 6.9.13.3 or newer**.

[[v9_upgrade_to_kotlin_2_2_21]]
==== Upgrade to Kotlin 2.2.21

The embedded Kotlin has been upgraded from 2.2.20 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.2.21[2.2.21].

[[v9_upgrade_to_jansi_2_4_2]]
==== Upgrade to Jansi 2.4.2

Jansi was upgraded from https://github.com/fusesource/jansi/blob/jansi-2.4.2/changelog.md#jansi-118-released-2019-04-02[1.18] to https://github.com/fusesource/jansi/releases/tag/jansi-2.4.2[2.4.2] to pick up support for Windows ARM64.

[[v9_upgrade_to_asm_9_9]]
==== Upgrade to ASM 9.9

ASM was upgraded from 9.8 to https://asm.ow2.io/versions.html[9.9] to ensure earlier compatibility for Java 26.

[[v9_upgrade_to_groovy_4_0_29]]
==== Upgrade to Groovy 4.0.29

Groovy has been updated to https://groovy-lang.org/changelogs/changelog-4.0.29.html[4.0.29].

[[v9_upgrade_to_jacoco_0_8_14]]
==== Upgrade to JaCoCo 0.8.14

JaCoCo has been updated to https://www.jacoco.org/jacoco/trunk/doc/changes.html[0.8.14].

[[v9_deprecations_9_3]]
=== Deprecations

[[deprecated_wrapper_get_available_distribution_types]]
==== Deprecation of the `Wrapper.getAvailableDistributionTypes()` method

The method on the link:{javadocPath}/org/gradle/api/tasks/wrapper/Wrapper.html[Wrapper] task has been deprecated and will be removed in Gradle 10.

Use link:{javadocPath}/org/gradle/api/tasks/wrapper/Wrapper.DistributionType.html#values()[`Wrapper.DistributionType.values()`] to obtain the available distribution types instead.

[[publishing_dependency_on_unpublished_project]]
==== Deprecation of publishing dependencies on unpublished projects

When publishing a project, Gradle resolves project dependencies to the coordinates of the target project's publication.
If the target project has no publication, Gradle currently resolves the dependency silently using that project's `group`, `name`, and `version`.

Starting with Gradle 10, this behavior is deprecated.
Gradle will no longer silently ignore the absence of a publication.
Publishing a project that depends on another project without a publication will be forbidden and will cause the build to fail.
This change prevents publishing broken metadata with dependency coordinates that cannot be resolved.

The example below demonstrates a build that triggers the deprecated behavior:

.build.gradle.kts
[source,kotlin]
----
plugins {
    id("java-library")
    id("maven-publish")
}

group = "com.example"
version = "1.0.0"

dependencies {
    api(project(":other"))
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
        }
    }
}
----

.other/build.gradle.kts
[source,kotlin]
----
plugins {
    id("java-library")
}

group = "com.example"
version = "1.0.0"
----

To avoid this deprecation, ensure that all project dependencies of published projects are also published.
In the example above, applying the `maven-publish` plugin and configuring a publication in the `:other` project resolves the issue:

.other/build.gradle.kts
[source,kotlin]
----
plugins {
    id("java-library")
    id("maven-publish")
}

group = "com.example"
version = "1.0.0"

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
        }
    }
}
----

[[deprecate_legacy_usage_values]]
==== Deprecation of legacy `Usage` attribute values

Since Gradle 5.6, the link:{javadocPath}/org/gradle/api/attributes/Usage.html[`Usage`] attribute has been split into an additional link:{javadocPath}/org/gradle/api/attributes/LibraryElements.html[`LibraryElements`] attribute.
In the JVM ecosystem, `Usage` indicates whether a variant is intended for compilation or runtime, while `LibraryElements` specifies the format of the artifact (for example, a JAR file or a classes directory).

To ease migration, Gradle has automatically mapped legacy `Usage` values to their corresponding `Usage` and `LibraryElements` pairs:

|===
| Legacy `Usage`           | Replaced `Usage` | Replaced `LibraryElements`
| `java-api-jars`          | `java-api`       | `jar`
| `java-api-classes`       | `java-api`       | `classes`
| `java-runtime-jars`      | `java-runtime`   | `jar`
| `java-runtime-classes`   | `java-runtime`   | `classes`
| `java-runtime-resources` | `java-runtime`   | `resources`
|===

Starting with Gradle 10, this automatic mapping will no longer occur when legacy `Usage` values are added directly to an `AttributeContainer` in build logic.
To maintain backward compatibility for already published modules, Gradle will continue translating legacy `Usage` values found in published Gradle Module Metadata.

[[module_identity_for_root_component]]
==== Deprecation of using module coordinates to depend on the current project

Starting with Gradle 10, declaring a dependency on the current project using module coordinates (`group`, `name`, `version`) will no longer resolve to that project.
Instead, Gradle will attempt to resolve that dependency from a repository.

The example below demonstrates the change in behavior:

.my-project/build.gradle.kts
[source,kotlin]
----
group = "com.example"
version = "1.0.0"

val deps = configurations.dependencyScope("deps")
val classpath = configurations.resolvable("classpath") {
    extendsFrom(deps.get())
    attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
}

val elements = configurations.consumable("elements") {
    attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
}

dependencies {
    // In Gradle 9.x, this dependency resolves to the `elements` configuration.
    // In Gradle 10, this dependency will attempt to resolve from a repository.
    deps("com.example:my-project:1.0.0")
}
----

To continue depending on the current project, use a `project` dependency:

.my-project/build.gradle.kts
[source,kotlin]
----
dependencies {
    // Declare a dependency on the current project to continue resolving
    // to the current project.
    deps(project)
}
----

[[deprecate_moduleversionselector_to_modulecomponentselector]]
==== Deprecation of `ModuleVersionSelector` to `ModuleComponentSelector` conversion

The conversion of `ModuleVersionSelector` to `ModuleComponentSelector` (used in
link:{javadocPath}/org/gradle/api/artifacts/ResolutionStrategy.html#force(java.lang.Object...)[`ResolutionStrategy.force(...)`],
link:{javadocPath}/org/gradle/api/artifacts/DependencyResolveDetails.html#useTarget(java.lang.Object)[`DependencyResolveDetails.useTarget(Object)`], and
link:{javadocPath}/org/gradle/plugin/management/PluginResolveDetails.html#useModule(java.lang.Object)[`PluginResolveDetails.useModule(Object)`]
) has been deprecated and will be removed in Gradle 10.0.0.

Typically, `ModuleVersionSelector` instances are `DependencyContstraint` objects.

Note that this deprecation does not apply to `ExternalDependency` objects, despite them implementing `ModuleVersionSelector`.

To fix this deprecation, pass one of the supported notations (for example, a String in the `group:name:version` format).

[[changes_9.2.0]]
== Upgrading from 9.1.0 and earlier

[[v9_potential_breaking_changes_9_2]]
=== Potential breaking changes

[[v9_upgrade_to_kotlin_2_2_20]]
==== Upgrade to Kotlin 2.2.20

The embedded Kotlin has been upgraded from 2.2.0 to link:https://github.com/JetBrains/kotlin/releases/tag/v2.2.20[Kotlin 2.2.20].

[[v9_removed_object_factory_dependency_collector]]
==== Removed incubating `ObjectFactory#dependencyCollector()` method

The incubating `ObjectFactory#dependencyCollector()` method has been removed.
You can still create `DependencyCollectors` within Gradle <<properties_providers.adoc#managed_types, managed types>>.

[[v9_consumable_configurations_initialized_lazily]]
==== Consumable configurations in bundled plugins are now initialized lazily

Consumable configurations created by bundled Gradle plugins are now initialized only when needed.
`Configure` actions on these configurations no longer run at configuration time by default.
They only execute if the configuration is published, consumed as a variant, or otherwise realized by build logic.

For example:

.build.gradle.kts
[source,kotlin]
----
plugins {
    id("java-library")
}

configurations.named("apiElements").configure {
    println("Configuring apiElements")
}
----

With this change, the `Configuring apiElements` line is no longer printed during configuration time unless `apiElements` is actually realized.

See <<declaring_configurations.adoc#declaring-dependency-configurations,Declaring Configurations>> for more guidance.

[[validate_plugins_java_version]]
==== `ValidatePlugins` now has stricter Java version requirements

The `ValidatePlugins` task must now run on a Java version that is supported by the Gradle daemon.
This change was made because the task depends on several core Gradle services, which may now be compiled to the same bytecode version supported by the daemon.

By default, the task’s convention has been updated:

* If your project’s toolchain is compatible, `ValidatePlugins` will use it.
* Otherwise, it will fall back to the Java version used to run Gradle.

If you explicitly set a toolchain like this:

[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
tasks.withType<ValidatePlugins>().configureEach {
    javaLauncher.set(
        project.javaToolchains.launcherFor {
            languageVersion.set(JavaLanguageVersion.of(17))
        }
    )
}
----
=====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
tasks.withType(ValidatePlugins).configureEach {
    javaLauncher.set(
        project.javaToolchains.launcherFor {
            languageVersion.set(JavaLanguageVersion.of(17))
        }
    )
}
----
=====

If the specified Java version is **not** compatible with the Gradle daemon, you must update it to a <<compatibility.adoc#java_runtime,compatible version>>.

[[v9_deprecations_9_2]]
=== Deprecations

[[project_container_methods]]
==== Deprecation of `Project.container(...)` methods

The link:{javadocPath}/org/gradle/api/Project.html#container-java.lang.Class-[`Project.container(...)`] methods are deprecated and will be removed in Gradle 10.
These methods manually create named domain object containers.

Use a <<properties_providers.adoc#mutable_managed_properties,managed property>> to let Gradle instantiate containers automatically.
If a managed property isn’t possible, use `ObjectFactory.domainObjectContainer(...)` (available since Gradle 5.5).
Unlike `Project.container(Class)`, the `ObjectFactory` version decorates container elements and makes them extension aware.

[[dependency_management_rules]]
==== Deprecation of ruleSource-based dependency management APIs

The link:{javadocPath}/org/gradle/model/RuleSource.html[`RuleSource`]-based dependency management APIs have been deprecated and will be removed in Gradle 10.0.0.

Deprecated APIs include:

- link:{javadocPath}/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.html#all(java.lang.Object)[`ComponentMetadataHandler.all(Object)`]
- link:{javadocPath}/org/gradle/api/artifacts/dsl/ComponentMetadataHandler.html#all(java.lang.Object)[`ComponentMetadataHandler.withModule(Object,Object)`]
- link:{javadocPath}/org/gradle/api/artifacts/ComponentSelectionRules.html#all(java.lang.Object)[`ComponentSelectionRules.all(Object)`]
- link:{javadocPath}/org/gradle/api/artifacts/ComponentSelectionRules.html#withModule(java.lang.Object,java.lang.Object)[`ComponentSelectionRules.withModule(Object,Object)`]

Use the alternative methods that accept a `ComponentMetadataRule` class or an `Action`.

[[deprecate_register_feature_no_java_plugin]]
==== Deprecation of calling `registerFeature` without applying the Java plugin

Creating a JVM feature with link:{javadocPath}/org/gradle/api/plugins/JavaPluginExtension.html#registerFeature(java.lang.String,org.gradle.api.Action)[`JavaPluginExtension#registerFeature`] before applying the Java plugin has been deprecated and will become an error in Gradle 10.0.0.

Ensure the Java plugin is applied before invoking `registerFeature`.
The following bundled plugins apply the Java plugin automatically:

- `java-library`
- `application`
- `groovy`
- `scala`
- `war`

[[changes_9.1.0]]
== Upgrading from 9.0.0 and earlier

[[v9_potential_breaking_changes_9_1]]
=== Potential breaking changes

[[v9_upgrade_to_asm_9_8]]
==== Upgrade to ASM 9.8

ASM was upgraded from 9.7.1 to https://asm.ow2.io/versions.html[9.8] to ensure earlier compatibility for Java 25.

[[v9_upgrade_to_groovy_4_0_28]]
==== Upgrade to Groovy 4.0.28

Groovy has been updated to https://groovy-lang.org/changelogs/changelog-4.0.28.html[Groovy 4.0.28].

[[v9_deprecations_9_1]]
=== Deprecations

[[dependency_multi_string_notation]]
==== Deprecation of multi-string dependency notation

In an effort to simplify and standardize the Gradle API, the multi-string dependency notation used in dependency management has been deprecated and will no longer be permitted in Gradle 10.
Gradle will primarily accept dependency declarations in the form of a single string, with each dependency coordinate separated by a colon.

Below are examples of the deprecated multi-string notation:

====
[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
dependencies {
    implementation(group = "org", name = "foo", version = "1.0")
    implementation(group = "org", name = "foo", version = "1.0", configuration = "conf")
    implementation(group = "org", name = "foo", version = "1.0", classifier = "classifier")
    implementation(group = "org", name = "foo", version = "1.0", ext = "ext")
}

testing.suites.named<JvmTestSuite>("test") {
    dependencies {
        implementation(module(group = "org", name = "foo", version = "1.0"))
    }
}
----
=====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
dependencies {
    implementation(group: 'org', name: 'foo', version: '1.0')
    implementation(group: 'org', name: 'foo', version: '1.0', configuration: 'conf')
    implementation(group: 'org', name: 'foo', version: '1.0', classifier: 'classifier')
    implementation(group: 'org', name: 'foo', version: '1.0', ext: 'ext')
}

testing.suites.test {
    dependencies {
        implementation(module(group: 'org', name: 'foo', version: '1.0'))
    }
}
----
=====
====

These declarations should be replaced with the single-string notation:

====
[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
dependencies {
    implementation("org:foo:1.0")
    implementation("org:foo:1.0") {
        targetConfiguration = "conf"
    }
    implementation("org:foo:1.0:classifier")
    implementation("org:foo:1.0@ext")
}

testing.suites.named<JvmTestSuite>("test") {
    dependencies {
        implementation("org:foo:1.0")
    }
}
----
=====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
dependencies {
    implementation("org:foo:1.0")
    implementation("org:foo:1.0") {
        targetConfiguration = "conf"
    }
    implementation("org:foo:1.0:classifier")
    implementation("org:foo:1.0@ext")
}

testing.suites.test {
    dependencies {
        implementation("org:foo:1.0")
    }
}
----
=====
====

In some cases, a complete single-string notation may not be known up front.
Instead of concatenating the coordinates into a new string, it is possible to use a link:{javadocPath}/org/gradle/api/artifacts/dsl/DependencyFactory.html[`DependencyFactory`] to create `Dependency` instances directly from the individual components:

====
[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
val group = "org"
val artifactId = "foo"
val version = "1.0"

configurations.dependencyScope("implementation") {
    dependencies.add(project.dependencyFactory.create(group, artifactId, version))
}
----
=====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
def group = "org"
def artifactId = "foo"
def version = "1.0"

configurations.dependencyScope("implementation") {
    dependencies.add(project.dependencyFactory.create(group, artifactId, version))
}
----
=====
====

[[reporting_extension_file]]
==== Deprecation of `ReportingExtension.file(String)`

The link:{javadocPath}/org/gradle/api/reporting/ReportingExtension.html#file(String)[`file()` method] on `ReportingExtension` has been deprecated and will be removed in Gradle 10.0.0.

Instead, use `ReportingExtension.getBaseDirectory()` with `file(String)` or `dir(String)`.

[[reporting_extension_api_doc_title]]
==== Deprecation of `ReportingExtension.getApiDocTitle()`

The link:{javadocPath}/org/gradle/api/reporting/ReportingExtension.html#getApiDocTitle()[`getApiDocTitle()` method] on `ReportingExtension` has been deprecated and will be removed in Gradle 10.0.0.

There is no direct replacement for this method.

[[set-all-jvm-args]]
==== Deprecation of `JavaForkOptions.setAllJvmArgs()`

The link:{javadocPath}/org/gradle/process/JavaForkOptions.html#setAllJvmArgs(java.util.List)[`setAllJvmArgs()` method] on `JavaForkOptions` and, by inheritance, on `JavaExecSpec` has been deprecated and will be removed in Gradle 10.0.0.

Instead, to overwrite existing JVM arguments, use:

* `JavaForkOptions.jvmArgs()`
* `JavaForkOptions.setJvmArgs()`
* Provide a <<incremental_build.adoc#sec:task_input_nested_inputs,`CommandLineArgumentProvider`>> to add arguments via `JavaForkOptions.getJvmArgumentProviders()`

Note that link:{javadocPath}/org/gradle/process/JavaForkOptions.html#setAllJvmArgs(java.util.List)[`setAllJvmArgs()` method] on `JavaForkOptions` cleared all fork options before setting `jvmArgs`.
The properties cleared included:

* System properties configured via `JavaForkOptions.systemProperties`
* JVM argument providers configured via `JavaForkOptions.jvmArgumentProviders`
* Argument providers configured via `JavaExecSpec.argumentProviders`
* Memory settings configured via `JavaForkOptions.minHeapSize` and `JavaForkOptions.maxHeapSize`
* All other JVM arguments configured via `JavaForkOptions.jvmArgs`
* The assertion and debug flags configured via `JavaForkOptions.enableAssertions` and `JavaForkOptions.debug`

If the arguments you provide to `setJvmArgs()` or `jvmArgs()` depend on any of the above properties being cleared, you will need to manually clear them.

Consider the following snippets for examples of how to implement this change:

====
[.multi-language-sample]
=====
.build.gradle.kts
[source, kotlin]
----
plugins {
    id("java")
}

tasks.register<JavaExec>("myRunTask") {
    jvmArgumentProviders.clear() // Clear existing JVM argument providers
    maxHeapSize = null // Clear max heap size
    jvmArgs = listOf("-Dfoo", "-Dbar") // Set new JVM arguments
}
----
=====
[.multi-language-sample]
=====
.build.gradle
[source, groovy]
----
plugins {
    id("java")
}

tasks.named('myRunTask', JavaExec) {
    jvmArgumentProviders.clear() // Clear existing JVM argument providers
    maxHeapSize = null // Clear max heap size
    jvmArgs = ["-Dfoo", "-Dbar"] // Set new JVM arguments
}
----
=====
====

[[archives-configuration]]
==== Deprecation of `archives` configuration

The `archives` configuration added by the <<base_plugin.adoc#base_plugin,`base` plugin>> has been deprecated and will be removed in Gradle 10.0.0.
Adding artifacts to the `archives` configuration will now result in a deprecation warning.

If you want the artifact to be built when running the `assemble` task, add the artifact (or the task that produces it) as a dependency on `assemble`:

.build.gradle.kts
[source,kotlin]
----
val specialJar = tasks.register<Jar>("specialJar") {
    archiveBaseName.set("special")
    from("build/special")
}

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

[[deprecate-visible-property]]
==== Deprecation of the `Configuration.visible` property

Prior to Gradle 9.0.0, any configuration with `isVisible()` returning `true` would implicitly trigger artifact creation when running the `assemble` task.
This behavior was removed in Gradle 9.0.0, and the `Configuration.visible` property no longer has any effect.
The property is now deprecated and will be removed in Gradle 10.0.0.
You can safely remove any usage of `visible`.

If you want the artifacts of a configuration to be built when running the `assemble` task, add an explicit task dependency on `assemble`:

.build.gradle.kts
[source,kotlin]
----
val specialJar = tasks.register<Jar>("specialJar") {
    archiveBaseName.set("special")
    from("build/special")
}

configurations {
    consumable("special") {
        outgoing.artifact(specialJar)
    }
}

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

[[deprecated-gradle-build-non-string-properties]]
==== Deprecation of non-string `projectProperties` in `GradleBuild` task

The `GradleBuild` task now deprecates using non-String values in `startParameter.projectProperties`.
While the type is declared as `Map<String, String>`, there was no strict enforcement, allowing non-String values to be set.
This deprecated behavior will be removed in Gradle 10.0.0.

If you are using non-String values in project properties, convert them to `String` representation:

====
[.multi-language-sample]
=====
.build.gradle.kts
[source,kotlin]
----
val myIntProp = 42

tasks.register<GradleBuild>("nestedBuild") {
    startParameter.projectProperties.put("myIntProp", "$myIntProp") // Convert int to String
}
----
=====
[.multi-language-sample]
=====
.build.gradle
[source,groovy]
----
def myIntProp = 42

tasks.register('nestedBuild', GradleBuild) {
    startParameter.projectProperties.put('myIntProp', "$myIntProp") // Convert int to String
}
----
=====
====

[[toolchain-project-properties]]
==== Deprecation of project properties for toolchain configuration

In previous versions of Gradle, you could configure toolchains using <<build_environment.adoc#sec:project_properties, project properties>> on the command line with the `-P` flag.
For example, to disable toolchain auto-detection, you could use `-Porg.gradle.java.installations.auto-detect=false`.
This behavior is deprecated and will be removed in Gradle 10.0.0.
Instead, you should specify these settings as <<build_environment.adoc#sec:gradle_configuration_properties, Gradle properties>> using the `-D` flag:

[source,text]
----
-Dorg.gradle.java.installations.auto-detect=false
----
