skip to content

NamedDomainObjectContainer DSLs

Authoring extensible named-collection DSLs with domain object containers, lazy registration, and polymorphic containers. Interviewers ask because it is how blocks like sourceSets and test suites are built.

on this pageshow

questions

6

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

open as a page

In a NamedDomainObjectContainer, what is the difference between register(name), create(name), and maybeCreate(name)?

level: middleimportance: must knowfreq 50%

basics

~10 s

create makes the element eagerly now. register defers creation until needed (configuration avoidance) and returns a provider. maybeCreate returns the existing element of that name or eagerly creates it if absent.

open as a page

Walk through authoring a custom extensible named-collection DSL with objects.domainObjectContainer: what does the element type need, how do you expose it, and what makes the nested block work?

level: seniorimportance: must knowfreq 35%

basics

~20 s

Define an abstract element type with a name constructor param and Property-typed fields, create the container with objects.domainObjectContainer(Type), add it as an extension, and Gradle auto-generates the myThings { foo { } } nested DSL keyed by name.

open as a page

How does configureEach differ from all{} and forEach on a NamedDomainObjectContainer, and why does the distinction matter for configuration avoidance?

level: middleimportance: should knowfreq 42%

basics

~10 s

configureEach registers a lazy action that runs per element only when each is realized, so it doesn't force creation. all{} and forEach are eager — they realize every element immediately, defeating configuration avoidance.

open as a page

What is a NamedDomainObjectProvider, how do you obtain one, and how does it keep container access lazy?

level: middleimportance: should knowfreq 38%

basics

~10 s

It's a lazy handle to a named element returned by register or named(name). Holding or configuring it (configure { }) doesn't realize the element; only get() does — so wiring stays deferred.

open as a page

When would you use an ExtensiblePolymorphicDomainObjectContainer, and how does registerFactory / registerBinding enable a polymorphic named DSL?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Use it when one named container must hold several distinct element types (like tasks). You register a factory or type binding per subtype, then users create typed elements by name with register(name, Type), getting a polymorphic DSL.

open as a page