skip to content

allprojects & subprojects Cross-Configuration

Cross-configuring every project from the root with allprojects and subprojects, and the coupling that creates. Interviewers ask because it is everywhere in older builds and now actively discouraged.

on this pageshow

questions

5

What do the allprojects{} and subprojects{} blocks do when placed in a root build.gradle.kts, and how do they differ?

level: juniorimportance: must knowfreq 70%

answer

  1. root Project helper methods
  2. allprojects = root + children
  3. subprojects = children only
  4. this = target project inside block
  5. eager configuration-phase iteration

basics

~10 s

Both apply configuration to many projects at once from the root build. allprojects{} includes the root plus every subproject; subprojects{} applies only to the child projects, excluding the root.

solid answer

~40 s

`allprojects {}` and `subprojects {}` are configuration blocks Gradle exposes on the root `Project`. Each takes an action that Gradle runs against a set of projects so you can cross-configure them in one place — typically applying a plugin, setting a `group`/`version`, or declaring common repositories and dependencies. The difference is which projects are in the set: `allprojects {}` iterates the root project **and** all of its descendants; `subprojects {}` iterates only the descendants and skips the root. You pick `subprojects {}` when the root is just an aggregator with no production code, and `allprojects {}` when even the root needs the setting (e.g. a shared `group`). Both are shorthand for calling `project.allprojects(action)` / `project.subprojects(action)`, which configure the targets eagerly during the configuration phase.

code

kotlin · 13 lines
kotlin
// root build.gradle.kts
allprojects {
    group = "com.example.shop"
    version = "2.3.1"
}

subprojects {
    apply(plugin = "java-library")
    repositories { mavenCentral() }
    dependencies {
        "testImplementation"("org.junit.jupiter:junit-jupiter:5.10.0")
    }
}

go deeper

for a junior

State the basic split: allprojects = root + all subprojects, subprojects = subprojects only, and that both let you configure many modules from the root.

for a middle

Explain that this is the target project, give a concrete use (apply java plugin + shared repos), and know the quoted-configuration gotcha.

for a senior

Frame them as eager helpers on the root Project, contrast with convention plugins, and note the coupling cost.

for a principal

Position cross-configuration as an anti-pattern at scale and discuss migration strategy to convention plugins for large monorepos.

## What they are In a Gradle multi-project build there is one **root project** (defined by `settings.gradle.kts`) and zero or more **subprojects** (declared with `include(":app", ":lib")`). Every project gets its own `Project` object and, usually, its own `build.gradle.kts`. The root `Project` exposes two helper methods that let you configure many projects from one file: - `allprojects(action)` — runs `action` against **this project and every project beneath it** (root + all subprojects). - `subprojects(action)` — runs `action` against **every project beneath this one** (subprojects only; the root is excluded). Written as DSL blocks in the root `build.gradle.kts`: ```kotlin allprojects { group = "com.example" version = "1.0.0" } subprojects { apply(plugin = "java") repositories { mavenCentral() } dependencies { "testImplementation"("org.junit.jupiter:junit-jupiter:5.10.0") } } ``` ## The key distinction The ONLY difference is the target set: | Block | Root project | Subprojects | |-------|-------------|-------------| | `allprojects {}` | yes | yes | | `subprojects {}` | no | yes | Use `subprojects {}` for things that only the *code* modules need (the `java` plugin, test dependencies) when the root is a pure aggregator. Use `allprojects {}` for things the root also needs — most commonly a shared `group` and `version` coordinate, or shared `repositories`. ## How it executes These are **not** declarative config that Gradle merges lazily. When the root build script runs during the configuration phase, `allprojects`/`subprojects` immediately schedule the action to run against each target project. Inside the block `this` is the *target* `Project`, so `apply(...)`, `dependencies {}`, `tasks` etc. all refer to that project, not the root. Because string-keyed methods like `dependencies` need the configuration to exist, you often see the quoted form `"implementation"(...)` in the root script — the `implementation` configuration is added by the `java` plugin you apply in the same block, so the typed accessor isn't generated for the root script. ## Why people reach for them They remove duplication: instead of repeating `repositories`, `group`, and common plugins in ten subproject scripts, you write it once. The trade-off is coupling — the root script now reaches into every child, which is exactly what convention plugins later replace.

  • Why might you write `"testImplementation"(...)` in quotes inside a subprojects block instead of the usual `testImplementation(...)`?
    The typed `testImplementation` accessor is generated only for scripts where the plugin that creates that configuration is applied directly. In the root script the configuration is added to the *target* project at runtime, so the root script has no generated accessor and you must reference it by its string name.
  • If you want a setting on every subproject except the root, which block do you use?
    `subprojects {}` — it excludes the root and applies only to descendants.

saying these in an interview costs you the question

  • Saying allprojects excludes the root — it includes the root.
  • Claiming these blocks apply lazily/declaratively; they iterate eagerly during configuration.

context

open as a page

Show how you would use subprojects{} to apply a common plugin, repositories, and a shared test dependency across all modules, and explain the configuration-name gotcha.

level: middleimportance: must knowfreq 60%

basics

~10 s

Put apply(plugin = "..."), a repositories {} block, and a dependencies {} block inside subprojects {}. Reference configurations like implementation by their string name because typed accessors aren't generated in the root script.

open as a page

Why is cross-configuring child modules via allprojects{}/subprojects{} in the root build considered a coupling problem, and what does it cost you?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Each child's build is partly defined in a file it doesn't own (the root), so you can't read a module's script and know its real config. It also forces every module to be configured whenever any is touched, hurting maintainability and laziness.

open as a page

How can you cross-configure a single named subproject (or a filtered subset) from the root build, rather than every project?

level: middleimportance: should knowfreq 40%

basics

~10 s

Use project(":path") { ... } to target one named subproject, or filter inside subprojects {} with a condition like if (name.startsWith("feature-")). allprojects/subprojects are the all-or-nothing versions.

open as a page

What is the difference between the `buildscript {}` block, `allprojects {}`, and `subprojects {}` in a root build, and where do plugin repositories belong?

level: seniorimportance: should knowfreq 35%

basics

~10 s

buildscript {} configures the build's own classpath/repositories (where Gradle finds plugins to apply). allprojects {}/subprojects {} configure the projects (their dependencies, plugins, settings). They solve different problems and aren't interchangeable.

open as a page