What does Gradle's deprecation of 'mutating another project's state' mean, and which common subprojects { } patterns does it target?
answer
- no mutating another project's state
- targets subprojects/allprojects + project(":x") { }
- afterEvaluate doesn't dodge it
- self-config (project(":lib") dependency) is fine
- fix = convention plugins, not suppression
basics
~20 sGradle is deprecating code that reaches into and changes another project's model from outside it — exactly what subprojects { }/allprojects { } and project(":x") { ... } blocks do. The fix is self-applied convention plugins.
solid answer
~50 sGradle has been moving to **disallow cross-project mutation** — code in one project (usually the root) that mutates *another* project's state during configuration. Under the incubating **Project Isolation** feature this is reported as a violation, and related access to other projects' mutable state has been emitting deprecation warnings. The patterns it targets are the classic shared-config idioms: `subprojects { ... }` and `allprojects { ... }` (which mutate all children from the root), and `project(":lib") { ... }` blocks that configure a *named* sibling from elsewhere. All of them apply plugins, add dependencies, or tweak tasks on a project that isn't the one running the code. The migration is consistent: relocate that logic into **convention plugins** in an included `build-logic` build and have each project apply the ones it needs, so the only thing mutating a project is code it applied itself.
code
kotlin · 9 lines// Deprecated: configuring a NAMED sibling from the root
project(":app") {
apply(plugin = "application")
dependencies { "implementation"(project(":lib")) }
}
// Allowed: :app configures ITSELF in its own build.gradle.kts
plugins { application; id("myorg.java-conventions") }
dependencies { implementation(project(":lib")) }go deeper
Recognize that subprojects/allprojects/project(":x") { } configure other projects and are being phased out.
Distinguish self-configuration (allowed) from mutating another project (deprecated), and name convention plugins as the fix.
Explain the Project Isolation enforcement path and why afterEvaluate doesn't escape it.
Plan an org-wide deprecation-cleanup with build-logic, and gate new cross-config out via CI/lint.
## The rule being enforced Gradle's direction is that, during configuration, **a project must not mutate the state of a different project**. This is the heart of the incubating **Project Isolation** feature, and Gradle has progressively added deprecation warnings for accessing/altering another project's mutable model. The motivation is the same throughout the family of topics: enable the **configuration cache** and **parallel, independent project configuration**. ## Which patterns are targeted The deprecation/violation hits the everyday "shared configuration" idioms: - **`allprojects { ... }`** and **`subprojects { ... }`** in the *root* build script — these run in the root but mutate every project (apply plugins, add dependencies, configure tasks). - **`project(":some:module") { ... }`** blocks — configuring a *specific named* project from the root (or anywhere that isn't that project). This is the most explicit form of cross-project mutation. - **`afterEvaluate { otherProject... }`** and similar deferred hooks that reach into another project — these don't escape the rule; they just delay the same violation. ```kotlin // All of these mutate a project from outside it: allprojects { repositories { mavenCentral() } } subprojects { apply(plugin = "java") } project(":app") { dependencies { "implementation"(project(":lib")) } } ``` ## What you are *allowed* to do Reading and mutating **your own** project is always fine. Declaring a **project dependency** (`implementation(project(":lib"))`) inside `:app`'s own script is fine — that's `:app` configuring itself, not mutating `:lib`. The line is: *who owns the code, and which project does it change?* ## The migration Lift each cross-config block into a **convention plugin**: ```kotlin // build-logic/src/main/kotlin/myorg.java-conventions.gradle.kts plugins { java } repositories { mavenCentral() } // then in each subproject build.gradle.kts: plugins { id("myorg.java-conventions") } ``` Now no project mutates another. The deprecation goes away because the violating pattern is gone — not suppressed. For configuring a *specific* sibling, the answer is usually to give that sibling its own build script (or convention plugin) rather than reaching into it from the root.
- Is declaring implementation(project(":lib")) deprecated?No. When it appears in :app's own build script, :app is configuring itself and merely referencing :lib as a dependency. The deprecation targets mutating :lib from outside it, not depending on it.
- Does wrapping cross-config in afterEvaluate avoid the deprecation?No. afterEvaluate only defers when the code runs; it still mutates another project's state, so it remains a cross-project-mutation violation (and often introduces ordering bugs).
saying these in an interview costs you the question
- Suppressing the warning instead of removing the cross-config
- Thinking project(":x") { } is fine because it's 'explicit'
- Believing a project dependency is itself a cross-project mutation