skip to content

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

level: middleimportance: must knowfreq 50%

answer

  1. create = eager now
  2. register = lazy, returns provider
  3. maybeCreate = get-or-create, eager
  4. configuration avoidance
  5. mirrors tasks.register vs tasks.create

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.

solid answer

~40 s

`create(name)` eagerly instantiates and configures the element immediately, returning the realized object. `register(name)` is the lazy, configuration-avoidant equivalent: it records intent and returns a `NamedDomainObjectProvider<T>`; the element is only realized when something queries the provider (`get()`) or otherwise forces it. Prefer `register` so unused elements (and their configuration closures) never run — this is the same configuration-avoidance principle behind `tasks.register` vs `tasks.create`. `maybeCreate(name)` is idempotent-on-presence: it returns the existing element if one with that name already exists, otherwise it eagerly creates one. It's useful when multiple plugins might contribute the same named element and you want to cooperate rather than fail with a duplicate-name error. The trade-off: `maybeCreate` is eager, so reserve it for genuine get-or-create scenarios.

code

kotlin · 10 lines
kotlin
val reports = objects.domainObjectContainer(Report::class.java)

// Lazy: 'html' configured only if realized
val html = reports.register("html") { enabled.set(true) }

// Cooperative get-or-create across plugins
val junit = reports.maybeCreate("junit")

// Eager realize when you truly need the object now
val xml = reports.create("xml")

go deeper

for a junior

Know create is eager and register is lazy; maybeCreate returns existing-or-new.

for a middle

Explain the provider return of register, the configuration-avoidance benefit, and the cooperative get-or-create role of maybeCreate.

for a senior

Discuss realization triggers, configuration-cache implications, and why register should be the default in plugin authoring.

for a principal

Set a team policy: register-by-default, reserve maybeCreate for multi-plugin contribution points, and audit eager create calls as configuration-time cost.

## The three methods ### create(name) / create(name) { ... } Eager. The element is instantiated and its configuration action runs *right now*, during configuration time. Returns the realized `T`. Calling `create` twice with the same name throws — names must be unique. ### register(name) / register(name) { ... } Lazy. Returns a `NamedDomainObjectProvider<T>` immediately, but the object is **not** created and the configuration action does **not** run until the provider is realized (e.g. `provider.get()`, a task that depends on it, or a `configureEach` that forces it). This is *configuration avoidance*: if nothing ever needs `foo`, its closure never executes, saving configuration-time work. This mirrors `tasks.register` versus the legacy `tasks.create`. ### maybeCreate(name) Get-or-create, eager. If an element named `name` already exists, it's returned as-is. If not, it's created (eagerly) and returned. No configuration action overload that's lazy — it realizes immediately. Use it when **cooperation** matters: plugin A and plugin B both want an element `shared`, and whoever runs second should reuse the existing one instead of erroring. ## Why laziness matters Gradle configures the *entire* build before running any task. Eagerly creating dozens of container elements — each running user closures, wiring properties, registering nested tasks — bloats configuration time and breaks with the configuration cache when work is done outside providers. `register` defers all of that until proven necessary. ## Realization triggers A registered element is realized by: `get()`/`getByName()`, `named(...).get()`, iterating the container eagerly (`forEach`, `all { }`), `findByName` (which realizes to inspect), or being wired into something that runs. `configureEach` and `named` stay lazy. ```kotlin val envs = objects.domainObjectContainer(Environment::class.java) // lazy — closure runs only if 'dev' is realized val devProvider = envs.register("dev") { url.set("https://dev") } // eager get-or-create — returns existing or makes one now val shared = envs.maybeCreate("shared") // eager — runs immediately envs.create("prod") { url.set("https://prod") } ``` ## Rule of thumb Default to `register`. Use `maybeCreate` only for genuine get-or-create. Avoid `create` in new code unless you specifically need the realized object inline.

  • Why is register preferred over create in a modern plugin?
    register defers element creation and its configuration closure until the element is actually needed (configuration avoidance), cutting configuration-time cost and playing well with the configuration cache; create realizes everything eagerly.
  • What does register return, and how do you later configure that element lazily?
    It returns a NamedDomainObjectProvider<T>. You configure it lazily with `provider.configure { ... }`, which queues the action without forcing realization until the provider is resolved.
  • When would maybeCreate be the right choice over register?
    When multiple plugins may contribute the same named element and you want get-or-create cooperation instead of a duplicate-name failure — and you're fine with eager realization.

saying these in an interview costs you the question

  • Claiming register creates the element immediately — it defers creation until realized.
  • Saying maybeCreate is lazy — it eagerly creates when the element is absent.
  • Recommending create as the default in new plugin code.

context