skip to content

What is a NamedDomainObjectContainer in Gradle, and why would a plugin author expose one in their extension?

level: juniorimportance: must knowfreq 55%

answer

  1. unique name = identity
  2. sourceSets/configurations are containers
  3. objects.domainObjectContainer(Type)
  4. generates a nested named DSL
  5. register/configureEach/named for free

basics

~10 s

It's a Gradle collection of objects each identified by a unique name. Plugins expose one so users can declare multiple named instances in a DSL block, like sourceSets or configurations.

solid answer

~30 s

A `NamedDomainObjectContainer<T>` is a Gradle collection where every element has a unique `name` property and is looked up by that name. It's the backbone of Gradle's named-collection DSLs — `sourceSets`, `configurations`, `tasks`, `publications` are all containers. A plugin author exposes one (via `objects.domainObjectContainer(Type)`) so end users can write a block like `myThings { foo { ... }; bar { ... } }`, where `foo` and `bar` become named, individually configurable elements. Containers give you name-based access, lazy `register`, bulk `configureEach`, and live filtering for free, instead of you hand-rolling a `Map<String, T>` and its configuration plumbing.

code

kotlin · 13 lines
kotlin
abstract class Environment(val name: String) {
    abstract val url: Property<String>
}

// In a plugin's apply():
val environments = objects.domainObjectContainer(Environment::class.java)
project.extensions.add("environments", environments)

// User build script:
// environments {
//     dev { url = "https://dev.example.com" }
//     prod { url = "https://example.com" }
// }

go deeper

for a junior

Define it as a named collection (like sourceSets) and know plugins expose one to let users declare multiple named items.

for a middle

Explain the unique-name constraint, name objects.domainObjectContainer(Type), and contrast with a plain Map (laziness, DSL, configureEach).

for a senior

Discuss how the DSL is generated, the T constructor/name-injection requirement, and when a container is the right model versus a simple property.

for a principal

Frame it as the canonical pattern for extensible plugin DSLs and how it integrates with configuration avoidance and live views across a large build.

## What it is A `NamedDomainObjectContainer<T>` is a specialized Gradle collection. Its defining constraint: every element `T` must have a `String getName()` (the name is fixed at creation and is the element's identity). The container indexes elements by that name, so lookups, creation, and the generated DSL all key off the name. You've already used several without realizing they were containers: `sourceSets`, `configurations`, `tasks` (a `TaskContainer`), `publications`. The reason the `sourceSets { main { ... }; integrationTest { ... } }` syntax works is precisely that `sourceSets` is a `NamedDomainObjectContainer<SourceSet>`. ## Why expose one When your plugin needs users to declare an *arbitrary number* of named things (multiple environments, multiple report definitions, multiple service endpoints), a container is the idiomatic answer. It gives you: - **A generated nested DSL** — `myExtension { dev { ... }; prod { ... } }` works automatically; each unknown name creates/configures an element. - **Name-based access** — `container.named("dev")`, `container.getByName("dev")`. - **Laziness** — `register(name)` defers creation until something needs the element (configuration avoidance). - **Bulk rules** — `configureEach { ... }` applies to every current and future element. - **Live, filtered views** — `matching { ... }`, `withType(...)`. ## Creating one The modern factory is on `ObjectFactory`: ```kotlin val things = objects.domainObjectContainer(Thing::class.java) ``` Gradle needs to instantiate `Thing` by name, so `Thing` must either have a constructor taking a single `String name` (Gradle injects the name), or you supply a `NamedDomainObjectFactory<Thing>`. ## Contrast with a plain Map A `Map<String, Thing>` gives you none of the above: no DSL, no laziness, no bulk configuration, no type-safe live views, no integration with Gradle's configuration-avoidance machinery. The container is the difference between a hand-rolled registry and a first-class Gradle DSL.

  • What requirement does the element type T have to satisfy for a NamedDomainObjectContainer?
    T must have a read-only `name` property (a `getName(): String`) that is unique within the container and fixed at creation; Gradle uses it as the element's identity and DSL key.
  • Name three built-in Gradle containers.
    `sourceSets` (NamedDomainObjectContainer<SourceSet>), `configurations` (ConfigurationContainer), `tasks` (TaskContainer); also `publications`, `repositories`.

Think of it like a hotel front desk keyed by guest name: each room (element) is addressed by a unique name, you can register a guest lazily, and you can issue one instruction to every room (configureEach).

saying these in an interview costs you the question

  • Saying a NamedDomainObjectContainer is just a List — it's keyed by unique name, not by index.
  • Claiming you need to hand-write the nested DSL — the container generates name-based DSL automatically.

context