What kinds of cross-project access does Project Isolation forbid, and how do you refactor a build to be isolation-compliant?
answer
- No mutating sibling tasks/extensions
- allprojects/subprojects is the anti-pattern
- Convention plugins for shared logic
- Lazy Provider/Property instead of eager reads
- Dependency + Configuration instead of cross-project task wiring
basics
~20 sIt forbids one project reaching into another's mutable model at configuration time — e.g. reading or mutating another project's tasks, extensions, or properties. You fix it by using dependencies, shared lazy Providers, and convention plugins instead.
solid answer
~40 sProject Isolation forbids a project from accessing another project's **mutable configuration-time state**: things like `project(':other').tasks`, `project(':other').extensions`, mutating a sibling's tasks, or eagerly reading a sibling's `version`/property. These are the patterns that couple projects together. To become compliant you replace direct access with isolated channels: declare real **dependencies** between projects and consume their **outputs via artifacts/variants**, pass values lazily through `Provider`/`Property` rather than reading another project eagerly, and push shared logic into **convention plugins** (`buildSrc` or an included build) so each project applies the same behavior without reaching across. Cross-project task wiring is replaced by dependency-based wiring or by consuming the producing task's output through a configuration. Some reads of another project's *immutable* identity (like its path) remain allowed; it's the mutable model and lazy-but-eager-resolved access that breaks isolation.
code
kotlin · 9 lines// BAD — breaks Project Isolation (root mutates every subproject)
subprojects {
tasks.withType<Test>().configureEach { useJUnitPlatform() }
}
// GOOD — a convention plugin each project applies itself
// buildSrc/src/main/kotlin/my.test-conventions.gradle.kts
tasks.withType<Test>().configureEach { useJUnitPlatform() }
// then in each project: plugins { id("my.test-conventions") }go deeper
Recognize that you can't reach into another project's tasks/properties; know convention plugins exist for shared config.
List concrete violations (sibling task mutation, allprojects/subprojects) and the convention-plugin fix.
Walk through refactoring patterns: conventions, lazy Providers, dependency/configuration wiring; distinguish allowed immutable reads from forbidden mutable access.
Lead a monorepo migration strategy — inventory cross-project coupling, sequence the convention-plugin rollout, and set guardrails so new violations don't creep back.
## What counts as a violation Project Isolation enforces that a project configures only itself. Violations are operations that touch another project's **mutable model during configuration**, for example: - `project(':lib').tasks.named("jar")` or mutating `project(':lib')`'s tasks/extensions. - Reading another project's mutable property eagerly, e.g. `val v = project(':lib').version`. - Cross-project `afterEvaluate` hooks that reconfigure a sibling. - A plugin that iterates `allprojects {}` / `subprojects {}` to mutate every project from the root. The `allprojects`/`subprojects` pattern is the classic anti-pattern: it mutates other projects from the root and is fundamentally incompatible with isolation. ## How to refactor ### 1. Replace cross-project mutation with convention plugins Instead of the root configuring every subproject via `subprojects {}`, write a **convention plugin** in `buildSrc` (or an included build) and apply it in each project's build script: ```kotlin // buildSrc/src/main/kotlin/my.java-conventions.gradle.kts plugins { `java-library` } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } ``` ```kotlin // lib/build.gradle.kts plugins { id("my.java-conventions") } ``` Each project applies the convention itself — no one reaches across. ### 2. Replace cross-project reads with lazy Providers Don't read a sibling's value eagerly. Pass values as `Provider<T>` so resolution is deferred and goes through Gradle's model rather than direct object access. Use `Property`/`Provider` and wire producers to consumers via task outputs. ### 3. Replace cross-project task wiring with dependency wiring If project A needs B's output, declare a **dependency** and consume B's artifact through a `Configuration` (resolvable on the consumer side, consumable on the producer side) rather than `project(':b').tasks.named(...)`. Gradle resolves the producing task automatically. ### 4. Avoid root-driven `allprojects`/`subprojects` blocks These mutate other projects and break isolation by design. Move that logic into conventions applied per-project. ## What's still allowed Reading another project's **immutable identity** (such as its project path) is generally fine. Declaring `project(":lib")` as a dependency is fine — that's the sanctioned channel. It's *mutable model access* and eager resolution of another project's state that isolation rejects. ## Why this matters Once these channels are the only way projects interact, Gradle can configure each project independently — in parallel and incrementally — which is the entire payoff of enabling Project Isolation.
- Why are allprojects {} / subprojects {} blocks incompatible with Project Isolation?They configure other projects' mutable models from the root, which is exactly the cross-project mutation Project Isolation forbids. Convention plugins applied per-project achieve the same shared behavior without reaching across boundaries.
- If project A needs a file produced by a task in project B, how do you wire it under isolation?Declare a dependency from A on B and expose B's output through a consumable Configuration/artifact; A consumes it via a resolvable configuration. Gradle wires the producing task automatically — no direct project(':b').tasks access.
saying these in an interview costs you the question
- Suggesting you 'just suppress' the violation rather than refactor the coupling.
- Claiming all cross-project references break (declaring a project dependency is allowed; mutable model access is what breaks).