skip to content

Shared Configuration

Sharing build logic across subprojects, and the trade-off between the old cross-configuration blocks and modern convention plugins. Interviewers use this area to gauge whether your Gradle knowledge is current.

on this pageshow

questions

30

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

What is the buildSrc directory in a Gradle build, and how does Gradle treat it?

level: juniorimportance: must knowfreq 70%

basics

~10 s

buildSrc is a special directory Gradle automatically compiles into a project and puts on every build script's classpath. You put shared build logic there — custom plugins, tasks, helper classes — without publishing anything.

open as a page

What does it mean to apply a convention plugin to a subproject, and how does it differ from configuring that subproject via subprojects {} in the root build?

level: juniorimportance: must knowfreq 60%

basics

~10 s

Each subproject opts in to shared config by listing the convention plugin in its own plugins {} block, e.g. id("myproject.java-conventions"). subprojects {} instead pushes config down from the root onto every subproject implicitly.

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

What is an included 'build-logic' build, and how does it differ from buildSrc for holding shared build logic?

level: middleimportance: must knowfreq 55%

basics

~10 s

build-logic is a separate Gradle build (a folder with its own settings.gradle) wired in via includeBuild('build-logic'). It holds convention plugins like buildSrc but is an explicit, isolated build instead of an implicit one.

open as a page

Why does a change to buildSrc slow down or invalidate caching for the whole build, and what does that imply?

level: middleimportance: must knowfreq 55%

basics

~20 s

buildSrc output is on every build script's classpath. Changing it changes that classpath, so Gradle must recompile and re-evaluate the build scripts and busts the build-script / configuration cache — making the next build slower everywhere.

open as a page

Inside a convention plugin in buildSrc, how do you add a dependency from the `libs` catalog, and how do `findLibrary`, `findBundle`, `findVersion`, and `findPlugin` differ?

level: middleimportance: must knowfreq 55%

basics

~10 s

Get VersionCatalogsExtension, call .named("libs"), then use findLibrary/findBundle/findVersion/findPlugin (each returns an Optional), .get() it, and pass libraries/bundles to dependencies.add(...).

open as a page

Why can't you write `libs.junit.jupiter` directly inside a convention plugin's `apply()` method in buildSrc, even though it works fine in a regular module's build.gradle.kts?

level: middleimportance: must knowfreq 60%

basics

~10 s

The type-safe libs.* accessors are generated for build scripts that the catalog is in scope for. Inside a buildSrc convention plugin those generated accessors aren't on the classpath, so libs.* doesn't resolve.

open as a page

How do you author a precompiled convention plugin in buildSrc, and how is it applied in a module?

level: middleimportance: must knowfreq 50%

basics

~10 s

Apply kotlin-dsl in buildSrc/build.gradle.kts, then add a *.gradle.kts file under buildSrc/src/main/kotlin. Its file name (minus the extension) becomes the plugin id, applied via plugins { id("...") }.

open as a page

What is 'project isolation' in Gradle, and why does cross-configuration with subprojects { } or allprojects { } violate it?

level: middleimportance: must knowfreq 55%

basics

~10 s

Project isolation means a project only reads/mutates its own state, never another project's. subprojects { } / allprojects { } reach into other projects' models from the root, coupling them and breaking that boundary.

open as a page

Why are convention plugins preferred over subprojects { }/allprojects { } for sharing build logic?

level: middleimportance: must knowfreq 50%

basics

~20 s

Convention plugins are applied by each project to itself, so they preserve project isolation, stay configuration-cache friendly, are explicit and opt-in per project, and are testable/versioned — unlike cross-config that mutates projects from the root.

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

Why is applying convention plugins per subproject considered the isolation-preserving alternative to subprojects {}? What concretely does it preserve?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Applying a plugin configures only the module that applies it, using only inputs the plugin itself knows. subprojects {} reaches across project boundaries at configuration time, coupling projects and breaking the isolation Gradle wants for safe parallel/lazy configuration.

open as a page

How does cross-configuration with subprojects { } / allprojects { } interfere with Gradle's configuration cache?

level: seniorimportance: must knowfreq 48%

basics

~10 s

The configuration cache assumes each project's configuration depends only on its own inputs. Cross-configuration makes the root produce other projects' config, breaking that per-project assumption and limiting safe reuse and parallel configuration.

open as a page

A teammate writes `libs.findLibrary("junit.jupiter")` in a convention plugin and it returns an empty Optional, even though `libs.junit.jupiter` works in build.gradle.kts. What's going on and how do you fix it?

level: juniorimportance: should knowfreq 40%

basics

~10 s

The programmatic lookup key is the original catalog alias (dashes), not the dotted accessor form. Use findLibrary("junit-jupiter"). The dots in libs.junit.jupiter are accessor navigation sugar, not the alias string.

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 are the classpath and caching tradeoffs between buildSrc and an included build-logic build?

level: middleimportance: should knowfreq 40%

basics

~20 s

buildSrc adds its jar to every build script's classpath (simple but global, and changes invalidate broadly). build-logic is consumed by id only where applied, giving narrower caching impact and explicit dependencies, at the cost of more setup.

open as a page

How would you define a reusable custom task type in buildSrc and use it from multiple modules?

level: middleimportance: should knowfreq 35%

basics

~10 s

Write a class extending DefaultTask under buildSrc/src/main/kotlin with @Input/@OutputFile-annotated properties, then in any module use tasks.register<MyTask>("name") to register and configure it.

open as a page

How do you compose multiple convention plugins on a single module, and how do you decide which conventions a given module should apply?

level: middleimportance: should knowfreq 40%

basics

~10 s

List several plugin ids in the module's plugins {} block — e.g. a base java-conventions plus a more specific application-conventions or library-conventions. The module applies only the presets matching its role.

open as a page

After applying a shared convention plugin, a single module needs a slightly different setting (say a higher Java toolchain). How do you handle that override cleanly?

level: middleimportance: should knowfreq 30%

basics

~20 s

Apply the convention plugin as usual, then add a normal configuration block in that one module's build script that resets the specific value — e.g. set java.toolchain to a higher version after the plugin runs.

open as a page

What does Gradle's deprecation of 'mutating another project's state' mean, and which common subprojects { } patterns does it target?

level: middleimportance: should knowfreq 38%

basics

~20 s

Gradle 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.

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

Why does an included build-logic build give better isolation and finer recompilation scope than buildSrc?

level: seniorimportance: should knowfreq 45%

basics

~20 s

buildSrc output is on every build script's classpath, so any change there can invalidate the whole build's caching. build-logic is a separate build consumed by plugin id, so changes touch only the dependent plugin paths.

open as a page

Walk through migrating shared build logic from buildSrc to an included build-logic build. What are the concrete steps and pitfalls?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Create build-logic/ with its own settings.gradle.kts, move convention plugin sources in, apply kotlin-dsl, add includeBuild('build-logic') to the root settings, then change subprojects to apply the plugins by id. Delete buildSrc once migrated.

open as a page

How does moving convention plugins into a build-logic build let you publish and share them across repositories, and what does that enable that buildSrc cannot?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Because build-logic is a real build with java-gradle-plugin projects, you can publish its convention plugins to a Maven repo and apply them from other repos by id. buildSrc is local-only and can't be published or reused outside its build.

open as a page

Some teams manage to use type-safe `libs.*` accessors inside their `build-logic` convention plugins. How is that possible, and what makes it work where plain buildSrc fails?

level: seniorimportance: should knowfreq 35%

basics

~20 s

By adding the catalog's generated accessor as a dependency of the build-logic build — declaring the catalog in dependencyResolutionManagement of build-logic's own settings and adding implementation(files(libs.javaClass...))/the generated dependency — so the accessor class lands on the plugin's compile classpath.

open as a page

What are common pitfalls and anti-patterns when using buildSrc in a large multi-project build?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Don't dump churny or production code in buildSrc, don't let it grow huge (every change rebuilds the world), avoid eager cross-project configuration from it, and keep its dependency surface tight. For big repos, graduate to a build-logic composite build.

open as a page

You inherit a build that shares config through a large subprojects {} block in the root. How would you migrate it to per-subproject convention plugin application, and what risks do you watch for?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Group the subprojects {} config by concern, expose each group as a convention plugin id, then add the matching ids to each module's plugins {} block and delete the root block. Watch for modules that quietly relied on root-injected config.

open as a page

Beyond isolation, what eager-evaluation and ordering pitfalls does cross-configuration cause, and how do convention plugins avoid them?

level: seniorimportance: should knowfreq 30%

basics

~10 s

subprojects { } eagerly visits every project, often forcing afterEvaluate hooks and order-dependent code that's fragile. Convention plugins configure one project lazily with configureEach/register, removing the ordering traps.

open as a page

Your convention plugin calls `versionCatalogs.named("libs")` and it throws because the catalog isn't named `libs`, or a second catalog exists. How do you reliably target the right catalog from inside the plugin?

level: middleimportance: nice to knowfreq 25%

basics

~10 s

named(name) must match the catalog's declared name exactly. If you have multiple or a custom name, pass that name; to be defensive, check catalogNames/find(name) first, or iterate, instead of assuming "libs".

open as a page