skip to content

Organizing Convention Plugins

Applying precompiled convention plugins per subproject, such as id('myproject.java-conventions'), instead of reaching down from the root with subprojects { }. Interviewers ask because it is the isolation-preserving way to share build logic in modern Gradle.

on this pageshow

questions

5

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%

answer

  1. opt-in plugins {} block
  2. id("myproject.java-conventions")
  3. self-describing module
  4. subprojects{} = external broadcast
  5. explicit vs implicit

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.

solid answer

~40 s

A 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
kotlin
// 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

for a junior

Know that a subproject opts in with a plugins {} entry like id("myproject.java-conventions"), and that this is the alternative to subprojects {}.

for a middle

Articulate explicit/local vs implicit/external, and why self-describing modules are easier to maintain.

for a senior

Tie it to project isolation and configuration avoidance; explain selective application across heterogeneous modules.

for a principal

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.

context

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

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