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?
answer
- opt-in plugins {} block
- id("myproject.java-conventions")
- self-describing module
- subprojects{} = external broadcast
- explicit vs implicit
basics
~10 sEach 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.
solid answer
~40 sA convention plugin bundles shared build configuration (Java version, common dependencies, test setup) behind a plugin id. To use it, a subproject adds it to its own `plugins { }` block, e.g. `id("myproject.java-conventions")`. This is an explicit, opt-in model: the subproject's own build script declares what conventions it wants, so reading that one file tells you everything applied to it. By contrast, `subprojects { }` in the root build reaches *into* every child and configures it from the outside. Nothing in the child's script reveals that config; you must read the root to understand the child. Convention plugins keep each module self-describing and let unrelated modules (a Java lib vs. an Android app) pick different conventions, whereas `subprojects {}` tends toward one-size-fits-all.
code
kotlin · 11 lines// settings.gradle.kts brings build-logic in (authored elsewhere)
includeBuild("build-logic")
// app/build.gradle.kts — opt in explicitly
plugins {
id("myproject.java-conventions")
}
dependencies {
implementation(project(":core"))
}go deeper
Know that a subproject opts in with a plugins {} entry like id("myproject.java-conventions"), and that this is the alternative to subprojects {}.
Articulate explicit/local vs implicit/external, and why self-describing modules are easier to maintain.
Tie it to project isolation and configuration avoidance; explain selective application across heterogeneous modules.
Frame it as the org-standard way to share build config at scale and govern consistency without coupling modules to the root.
## The problem being solved In a multi-project Gradle build you have a root project and several subprojects (modules). Most modules share config — the same Java toolchain, the same test framework, the same group/version, common plugins. You need a way to share that config without copy-pasting it into every `build.gradle.kts`. There are two broad strategies: 1. **Cross-configuration** — the root build uses `subprojects { ... }` (or `allprojects { ... }`) to reach down and configure children from the outside. 2. **Convention plugins** — the shared config is packaged behind a plugin id, and each subproject *applies* that plugin in its own `plugins { }` block. This topic is about (2): **applying** an already-authored convention plugin. ## What "applying" looks like A convention plugin has an id, conventionally namespaced like `myproject.java-conventions`. A subproject opts in by listing it: ```kotlin // app/build.gradle.kts plugins { id("myproject.java-conventions") } ``` That single line pulls in everything the plugin configures: the Java plugin, a toolchain, common test dependencies, repositories, and so on. The subproject only declares what is *specific* to it on top. ## Why this is different from subprojects { } `subprojects { }` is **implicit and external**. The block lives in the *root* build script and mutates every child. If you open `app/build.gradle.kts` you see almost nothing — yet the module has a Java toolchain, dependencies, and test config injected from elsewhere. To understand the module you must read a different file. Applying a convention plugin is **explicit and local**. The `plugins { }` block in the module *is* the declaration. The module is self-describing: what you see is what is applied. ## Why explicitness matters - **Readability** — each module's effective config is discoverable from its own script. - **Selective application** — module A applies `java-conventions`, module B applies `application-conventions`; you are not forced to apply one rule to all. - **Isolation / configuration avoidance** — `subprojects {}` forces eager cross-project access that fights against project isolation and parallel/lazy configuration; per-subproject plugin application avoids reaching across project boundaries. ## Mental model Think of a convention plugin as a *named preset* of build behavior. `subprojects {}` is a broadcast that hits everyone; a convention plugin is a label each module chooses to wear. Same shared logic — opposite direction of control.
- If a module needs everything in java-conventions plus the application plugin, how do you express that?Apply both ids in the module's plugins block — e.g. id("myproject.java-conventions") and application — or apply a separate application-conventions plugin that itself builds on the java one. Composition is just listing multiple plugin ids.
- Where does the convention plugin's id come from?From the build-logic / buildSrc that authored it; the id is derived from the precompiled script filename. Authoring is a separate concern — here we only consume the id.
subprojects {} is a building-wide announcement forced on every office; a convention plugin is a sign each office chooses to hang on its own door.
saying these in an interview costs you the question
- Claiming convention plugins and subprojects {} are interchangeable styles with no real difference.
- Thinking applying a convention plugin requires editing the root build script.