A teammate puts shared subproject conventions (Java version, common dependencies) directly in settings.gradle.kts. Why is that wrong, and where should they go?
answer
- Settings has no project-config API
- init phase = no Project yet
- subprojects/allprojects = cross-project, discouraged
- convention plugins in buildSrc
- config-cache friendliness
basics
~10 sSettings can't configure projects — it only declares structure. Shared conventions belong in the root build script (subprojects/allprojects) or, better, in convention plugins applied per project.
solid answer
~50 sSettings is evaluated in the initialization phase, before any `Project` exists to configure, and the `Settings` object has no `dependencies`, `plugins`, or `tasks` members. So you simply can't apply a Java toolchain or add dependencies there — those are `Project` concerns. The quick fix is the **root** `build.gradle.kts` using `subprojects { ... }` or `allprojects { ... }`, since the root project *is* a `Project` and can reach into its children during configuration. But cross-project configuration this way is discouraged: it couples projects, fights the configuration-cache, and hides logic in the root. The idiomatic answer is **convention plugins** — precompiled script plugins in `buildSrc` or an included build (`my.java-conventions.gradle.kts`) that each subproject applies via `plugins { id("my.java-conventions") }`. That keeps each project self-describing, reuses logic, and plays well with the configuration cache. Settings stays limited to structure and resolution.
code
kotlin · 6 lines// buildSrc/src/main/kotlin/my.java-conventions.gradle.kts
plugins { java }
java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) }
// service/build.gradle.kts
plugins { id("my.java-conventions") }go deeper
Just know settings is for structure and you can't put dependencies there.
Name the root build script with subprojects { } as the quick fix and mention convention plugins exist.
Contrast cross-project configuration with convention plugins, cite configuration-cache and self-describing-project benefits, and place buildSrc.
Define a build-architecture standard: settings = structure/resolution, convention plugins (shared via included build) = behaviour, ban allprojects in new builds, and justify via cache + governance + scaling.
## Why settings can't hold conventions During the **initialization phase**, Gradle builds the `Settings` object and learns the project tree. No `Project` is configured yet, and `Settings` exposes none of the project-configuration API (`dependencies`, `plugins`, `tasks`, toolchains). Trying to set a Java version or add a dependency in settings is a category error — there's no project to apply it to. ## Option A: root build script (works, but discouraged) The root `build.gradle.kts` is a real `Project`, so it can configure its children: ```kotlin // root build.gradle.kts subprojects { apply(plugin = "java") extensions.configure<JavaPluginExtension> { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } } ``` `subprojects { }` configures every subproject; `allprojects { }` includes the root too. This is *cross-project configuration*: it couples the root to the internals of every child, makes projects non-self-describing, and is harder for the **configuration cache** to reason about because one project's configuration mutates others. ## Option B: convention plugins (idiomatic) Extract shared logic into a **precompiled script plugin** in `buildSrc` (or an included build): ```kotlin // buildSrc/src/main/kotlin/my.java-conventions.gradle.kts plugins { java } java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) } dependencies { "implementation"("org.slf4j:slf4j-api:2.0.13") } ``` Each subproject opts in: ```kotlin // service/build.gradle.kts plugins { id("my.java-conventions") } ``` Benefits: each project declares its own behaviour (self-describing), logic is DRY and testable, and there's no cross-project mutation, so the configuration cache stays effective. ## The clean boundary - **settings.gradle.kts** — structure (`include`), plugin/dependency resolution policy, version catalogs. - **convention plugins** — reusable per-project behaviour. - **per-project build.gradle.kts** — what *this* project builds, applying conventions. - **root build script** — minimal; avoid `subprojects/allprojects` for new builds. The teammate's instinct (centralise) is right; the location is wrong. Centralise *behaviour* in plugins, not in settings.
- What's the difference between subprojects { } and a convention plugin for sharing config?subprojects { } imperatively configures children from the root (cross-project coupling, weaker config-cache behaviour). A convention plugin is applied per-project so each project is self-describing and the logic is reusable and testable.
- Why are convention plugins friendlier to the configuration cache?They configure only the project that applies them, avoiding one project mutating another's state — which is the kind of cross-project access the configuration cache restricts.
- Where do convention plugins typically live?In buildSrc (auto-available to all build scripts) or in a separate included build for sharing across repos.
Settings is the building's floor plan (which rooms exist); furnishing every room the same way is a job for a shared furnishing crew (convention plugin), not something you draw on the floor plan.
saying these in an interview costs you the question
- Claiming you can apply plugins or add dependencies in settings.gradle.kts.
- Recommending allprojects { } as the modern best practice without caveats.
- Confusing convention plugins with the version catalog.