skip to content

How and why would a Settings plugin call includeBuild, and what does composite-build inclusion accomplish?

level: seniorimportance: should knowfreq 38%

answer

  1. includeBuild = composite build
  2. automatic dependency substitution
  3. pluginManagement.includeBuild for plugins
  4. build-logic convention pattern
  5. no publish step needed

basics

~20 s

includeBuild(path) in a settings plugin wires another Gradle build into this one as a composite build. Gradle substitutes published dependencies with the included build's projects, letting you develop and consume a library or plugin together without publishing.

solid answer

~40 s

`includeBuild("...")` is a Settings API (`Settings.includeBuild`) that composes a separate, complete Gradle build into the current one — a **composite build**. A `Plugin<Settings>` can call `settings.includeBuild(path)` to standardize this wiring. Gradle automatically **substitutes** any external dependency whose coordinates match a project in the included build with that project's local output, so you can develop a library or convention plugin alongside its consumers with no publish step. There are two flavors: a regular `includeBuild` (for dependency substitution) and `pluginManagement { includeBuild(...) }`, which is specifically for sourcing *plugins* from an included build during initialization. The trade-off is configuration-time cost and complexity, so it's best for local dev or tightly-coupled monorepo-style setups rather than every CI build.

code

kotlin · 8 lines
kotlin
// In a settings plugin: include build-logic so its plugins resolve,
// and a sibling library so its dependency is substituted.
override fun apply(settings: Settings) {
    settings.pluginManagement {
        includeBuild("build-logic")   // supplies convention plugins
    }
    settings.includeBuild("../shared-lib")  // substitutes com.example:shared-lib
}

go deeper

for a junior

Recognize includeBuild composes another build in and that it's a settings-level call.

for a middle

Explain dependency substitution and the build-logic + pluginManagement.includeBuild pattern.

for a senior

Distinguish top-level vs pluginManagement includeBuild, discuss when substitution fires, and the local-dev vs CI trade-off.

for a principal

Decide composite-build strategy for a multi-repo org: when to include vs publish, gating in CI, and standardizing it through a settings plugin.

## Composite builds A **composite build** (a.k.a. included build) is a build that includes other complete Gradle builds. `includeBuild("../shared-lib")` in `settings.gradle.kts` — or `settings.includeBuild(...)` from a `Plugin<Settings>` — tells Gradle to treat `../shared-lib` as part of this build. The key mechanic is **dependency substitution**: if your build declares `implementation("com.example:shared-lib:1.0")` and the included build produces a project publishing `com.example:shared-lib`, Gradle silently replaces the binary dependency with the included project's compiled output. You edit both, build once, no `publishToMavenLocal`. ## Two distinct uses ### 1. Regular includeBuild — substitute dependencies ```kotlin // settings.gradle.kts (or settings.includeBuild from a plugin) includeBuild("../shared-lib") ``` ### 2. pluginManagement.includeBuild — supply plugins To *develop a plugin* and use it in the same tree, the included build must be visible during initialization, so it goes inside `pluginManagement`: ```kotlin pluginManagement { includeBuild("build-logic") } plugins { id("com.example.conventions") // resolved from build-logic } ``` This is the idiomatic `build-logic` pattern: convention plugins live in an included build and are applied across all subprojects. ## From a settings plugin ```kotlin class MonorepoSettingsPlugin : Plugin<Settings> { override fun apply(settings: Settings) { settings.includeBuild("../platform") // dependency substitution wires platform projects in automatically } } ``` ## When to use it - **Good for:** local iterative development across repos, monorepo-ish layouts, developing a plugin alongside consumers. - **Watch out for:** added configuration time, more complex dependency graphs, and that substitution only fires when coordinates actually match. Overusing it in CI can slow builds; many teams include the build only behind a local property/flag.

  • What is the difference between includeBuild at the top level and inside pluginManagement?
    Top-level includeBuild substitutes regular dependencies. pluginManagement.includeBuild makes the included build available during initialization so it can supply plugins for the plugins {} DSL.
  • When does dependency substitution actually take effect?
    Only when an external dependency's group:name matches the coordinates a project in the included build publishes. If they don't match, no substitution happens and the binary is used.
  • What's a downside of broadly applying includeBuild in CI?
    It increases configuration time and graph complexity; teams often gate it behind a flag and consume published artifacts in CI while including builds only for local development.

includeBuild is like editing a dependency's source live inside your project instead of waiting for it to ship a new release jar to a warehouse you then re-order from.

saying these in an interview costs you the question

  • Claiming includeBuild just runs another build's tasks — its core value is dependency substitution.
  • Putting a plugin-supplying includeBuild at the top level instead of inside pluginManagement and being surprised the plugin isn't found.

context