skip to content

When would you choose includeFlat over a hierarchical include layout or a composite build (includeBuild)? What are the trade-offs?

level: seniorimportance: should knowfreq 25%

answer

  1. side-by-side checkouts, one build
  2. include = monorepo, self-contained
  3. includeBuild = independent builds + substitution
  4. flat = fragile clone reproducibility
  5. one Settings vs many

basics

~10 s

Use includeFlat when projects are checked out side-by-side as separate folders but must share one build. Use hierarchical include for a single repo, and composite builds (includeBuild) for independent builds with their own settings.

solid answer

~40 s

`includeFlat` fits the legacy 'side-by-side checkouts' model: several projects live as siblings under a common parent (often separate VCS checkouts) but participate in **one** build with **one** `settings.gradle.kts`. It avoids forcing every project under a single root directory. Compared with hierarchical `include`, it trades a clean self-contained repo for flexibility in on-disk arrangement — at the cost of a build that's harder to clone reproducibly (you must check out the siblings in the right relative positions). Compared with a **composite build** (`includeBuild`), `includeFlat` keeps everything in one build/one project graph (shared configuration, one `Settings`), whereas `includeBuild` keeps each build fully independent with its own settings, substituting published dependencies on the fly. Today most teams prefer monorepo `include` or composite builds; `includeFlat` is mostly seen in older builds or where separate-repo-but-shared-build is a hard requirement.

code

kotlin · 5 lines
kotlin
// includeFlat: one build, sibling dirs
includeFlat("shared-protocol")

// vs composite build: independent build, dependency substitution
includeBuild("../shared-protocol")

go deeper

for a junior

Know includeFlat is for side-by-side project folders sharing one build.

for a middle

Contrast it with hierarchical include and note the single-Settings property.

for a senior

Compare against composite builds on coupling, reproducibility, and configuration sharing; recommend the right tool per scenario.

for a principal

Set org-wide repo topology policy: when monorepo include, when composite builds, and whether flat layouts are permitted given CI/clone reproducibility constraints.

## Three layout strategies **1. Hierarchical (`include`)** — everything under one root directory, one repo. Self-contained: `git clone` gives you a build that works. This is the default and the recommendation for monorepos. **2. Flat (`includeFlat`)** — projects are siblings on disk (`../foo`), but still one build with one root `settings.gradle.kts`. Historically used when each project was a separate checkout in a shared workspace folder. The build assumes the siblings exist at the expected relative paths. **3. Composite (`includeBuild`)** — each participant is a **fully independent build** with its own `settings.gradle.kts`. The including build substitutes external dependency coordinates (`group:name`) with the local build's outputs. Builds stay decoupled; you can develop a library and its consumer together without publishing. ## Trade-offs | Concern | include (hierarchical) | includeFlat | includeBuild (composite) | |---|---|---|---| | Number of Settings | one | one | one per build | | Reproducible clone | strong (single repo) | weak (need siblings placed correctly) | medium (declared build paths) | | Coupling | tight (one model) | tight (one model) | loose (dependency substitution) | | Typical use | monorepo | legacy side-by-side checkouts | multi-repo dev with substitution | ## Why includeFlat is rarely the best modern choice - **Fragile checkout**: CI and new contributors must reproduce the exact sibling layout; nothing in the repo guarantees it. - **Shared model can leak**: all flat projects share the same `allprojects`/`subprojects` configuration and plugin context, so an unrelated sibling inherits build conventions. - **Composite builds solve the multi-repo case better**: they keep builds independent and use dependency substitution, which is more robust for genuinely separate components. ## When includeFlat still earns its place When you truly want **one build graph** spanning side-by-side directories — e.g. an internal tool that must compile sources from a sibling directory you don't want to move, and you don't need the isolation a composite build provides. In that narrow case `includeFlat` is the most concise expression. ```kotlin // One build spanning siblings includeFlat("protocol-definitions") // ../protocol-definitions include("server", "client") // under root ```

  • Why are composite builds usually preferred over includeFlat for separate repos?
    includeBuild keeps each build independent with its own settings and substitutes published dependencies locally, so the components stay decoupled and each remains buildable on its own.
  • What is the biggest operational risk of a flat layout?
    Reproducibility: the build assumes sibling directories exist at fixed relative paths, but nothing in a single checkout enforces that, so CI and new clones can break.
  • Does includeFlat give shared configuration across the flat projects?
    Yes — they share one Settings and the root's allprojects/subprojects configuration, unlike composite builds which stay isolated.

saying these in an interview costs you the question

  • Recommending includeFlat as the default for new multi-repo setups (composite builds usually fit better).
  • Claiming includeFlat keeps builds independent — it does not; they share one Settings.
  • Ignoring the checkout-reproducibility problem of sibling layouts.

context