skip to content

Why are root-level allprojects {} and subprojects {} blocks discouraged under Project Isolation, and what replaces them?

level: juniorimportance: should knowfreq 22%

answer

  1. Root mutates siblings = violation
  2. allprojects/subprojects forbidden
  3. Convention plugin in buildSrc
  4. Each project applies it via plugins { id(...) }
  5. Independent, parallel-configurable projects

basics

~10 s

Those blocks configure other projects from the root, which is exactly the cross-project mutation Project Isolation forbids. Replace them with convention plugins in buildSrc that each project applies itself.

solid answer

~40 s

`allprojects {}` and `subprojects {}` live in the **root** build script and reach down to configure every other project's mutable model. Project Isolation forbids one project from mutating another at configuration time, so these blocks are direct violations — they're the most common reason a build fails isolation. The replacement is a **convention (precompiled script) plugin**: put the shared logic in `buildSrc/src/main/kotlin/my.conventions.gradle.kts` (or an included build), then have each project `apply` it via `plugins { id("my.conventions") }`. Now each project configures **itself** with the shared behavior — no cross-project mutation — which keeps projects independent and lets Gradle configure them in parallel and incrementally. As a bonus, convention plugins are testable, reusable across builds, and don't rely on project evaluation order.

code

kotlin · 7 lines
kotlin
// buildSrc/src/main/kotlin/my.java-conventions.gradle.kts
plugins { `java-library` }
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } }
tasks.withType<Test>().configureEach { useJUnitPlatform() }

// each subproject's build.gradle.kts
plugins { id("my.java-conventions") }

go deeper

for a junior

Know that root allprojects/subprojects mutate other projects and that convention plugins applied per-project replace them.

for a middle

Explain why the mutation breaks isolation and how to structure conventions in buildSrc.

for a senior

Discuss migration: splitting conventions, included builds, and avoiding reintroducing root mutation.

for a principal

Define the org convention-plugin standard so large builds stay isolation-compliant by default.

## What these blocks do In many older Gradle builds the root `build.gradle(.kts)` contains: ```kotlin subprojects { apply(plugin = "java-library") tasks.withType<Test>().configureEach { useJUnitPlatform() } } ``` This runs from the **root project** and **mutates every subproject's model**. It's convenient but it means the root is configuring code that belongs to other projects. ## Why Project Isolation rejects it Project Isolation's core rule: a project may only configure **itself**. `allprojects`/`subprojects` violate this by definition — the root mutates siblings. That coupling is what prevents Gradle from configuring projects independently, so isolation reports it as a violation. (It's also fragile without isolation: it depends on evaluation order and makes builds hard to reason about.) ## The replacement: convention plugins Move shared configuration into a **precompiled script plugin** in `buildSrc`: ```kotlin // buildSrc/src/main/kotlin/my.java-conventions.gradle.kts plugins { `java-library` } java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } tasks.withType<Test>().configureEach { useJUnitPlatform() } ``` Then each project opts in: ```kotlin // lib/build.gradle.kts plugins { id("my.java-conventions") } ``` Now: - Each project applies the convention to **itself** — no cross-project mutation. - Projects stay independent, so Gradle can configure them in parallel and cache each separately. - The convention is explicit per project (you can see what applies), testable, and reusable across builds (publish it from an included build if needed). ## Migration tips - Group related conventions into multiple small plugins (java, testing, publishing) rather than one mega-plugin. - Apply them by `id` in each project, not via `apply(from = ...)` of arbitrary scripts. - Avoid putting back any `allprojects`/`subprojects` mutation 'just for one thing' — it reintroduces the violation. ## Net effect Replacing root-driven mutation with per-project convention application is usually the single biggest step in making a build Project-Isolation-compliant.

  • Is allprojects {} ever acceptable if it only reads, never mutates?
    Even reads can pull in another project's mutable model and they encourage coupling; under Project Isolation the safe approach is per-project convention plugins. Prefer eliminating the block entirely.
  • Where should a convention plugin live?
    In buildSrc (auto-available to the build) or in a separate included build you composite in via includeBuild — both let each project apply it by id without cross-project mutation.

It's the difference between a manager personally rearranging everyone's desk (allprojects) versus handing each person the same checklist to set up their own desk (convention plugin) — the latter scales and lets everyone work at once.

saying these in an interview costs you the question

  • Saying allprojects/subprojects is fine as long as it's in the root (the root mutating siblings is the violation).
  • Replacing the block with cross-project apply(from = ...) hacks instead of real convention plugins.

context