skip to content

How does Gradle disambiguate task addressing when the root build and an included build both have a project or task with the same name?

level: middleimportance: should knowfreq 35%

answer

  1. first segment = build name
  2. per-build isolated namespaces
  3. duplicate subproject names are fine
  4. duplicate BUILD names fail
  5. override with name = ...

basics

~20 s

The first path segment is the build name. :lib:app:run targets the included build lib; :app:run targets the root build's app subproject. The build-name prefix removes ambiguity, so equal subproject names in different builds don't clash.

solid answer

~40 s

Each build in a composite has its own isolated project namespace, so identical project or task names across builds never collide — they live in different builds. Addressing resolves the leading segment as a **build name** first: `:lib:app:run` is the `run` task in subproject `app` of the *included* build `lib`, while `:app:run` (no included-build prefix) is `run` in subproject `app` of the **root** build. The risk is purely the **build name** itself: if two included builds default to the same directory name (e.g. two `utils` directories), Gradle reports a duplicate-build-name conflict at configuration time, and you must override one via `includeBuild(...) { name = "..." }`. So disambiguation is structural: pick unique build names, and every task is then uniquely addressable as `:<uniqueBuildName>:<project-path>:<task>`.

code

kotlin · 4 lines
kotlin
// settings.gradle.kts — resolve a default-name collision
includeBuild("../team-a/utils") { name = "a-utils" }
includeBuild("../team-b/utils") { name = "b-utils" }
// now both addressable:  ./gradlew :a-utils:build :b-utils:build

go deeper

for a junior

Know that the leading segment is a build name and projects are isolated per build.

for a middle

Explain that duplicate subproject names are harmless but duplicate build names conflict, and how to override the name.

for a senior

Reason about how unique build names keep both CLI and programmatic addressing deterministic.

for a principal

Define naming conventions across teams' included builds to avoid systemic collisions in large composites.

## Namespaces are per-build In a composite, every included build is a **separate, isolated build** with its own root project, subprojects, and task containers. There is no global flattening. So an included build named `lib` and the root build can *both* have a subproject `app` with a `run` task — these are two distinct tasks living in two distinct builds. ## How a task path is resolved Gradle parses a path like `:lib:app:run` left to right: 1. The leading `:` followed by a segment is checked against **included build names** first when running a composite. 2. If the first segment matches an included build name (`lib`), the remainder (`:app:run`) is resolved *inside that build*. 3. If it does not match an included build, it is resolved as a project path inside the **root** build (`:app:run` → root build's `app` subproject). This is why two builds can each own an `app:run` without conflict — the build-name prefix selects the namespace. ## The real collision: duplicate build names The genuine ambiguity is at the **build level**. If you include two builds whose default names collide: ```kotlin includeBuild("../team-a/utils") includeBuild("../team-b/utils") // both default to name "utils" ``` Gradle fails the configuration with a build-name conflict. Fix it by overriding: ```kotlin includeBuild("../team-a/utils") { name = "a-utils" } includeBuild("../team-b/utils") { name = "b-utils" } ``` Now `:a-utils:build` and `:b-utils:build` are unambiguous. ## Programmatic equivalent The same uniqueness rule governs `gradle.includedBuild("a-utils")` — the lookup is by build name, so names must be unique for the handle lookup to be deterministic. ## Summary rule Keep **build names unique**; then `:<buildName>:<projectPath>:<task>` is a globally unique address, and no amount of duplicate *subproject* or *task* names inside different builds causes a clash.

  • Can two included builds have a subproject with the same name?
    Yes. They live in separate builds, so `:lib1:core:test` and `:lib2:core:test` are distinct and unambiguous as long as the build names differ.
  • What forces you to set an explicit `name` on an included build?
    When two included builds would otherwise default to the same name (e.g. same directory name). Gradle reports a build-name conflict until you override one.

saying these in an interview costs you the question

  • Claiming included builds share a global project namespace.
  • Thinking duplicate task names across builds cause errors — only duplicate build names do.

context