How do you give an extension nested configuration blocks (a sub-block inside the extension's block) in modern Gradle?
answer
- extension is ExtensionAware too
- child: ext.extensions.create(...)
- @Nested managed getter
- ObjectFactory materializes nested
- NamedDomainObjectContainer for many
basics
~10 sEither expose a @Nested property that Gradle materializes (managed nested object), or, because an extension is itself ExtensionAware, register a child extension on it via extension.extensions.create(...) so it gets its own nested DSL block.
solid answer
~50 sThere are two complementary ways to get a nested block like `greeting { server { url = ... } }`. First, because every extension created via `extensions.create()` is itself **ExtensionAware**, the plugin can register a child extension on it: `greetingExt.extensions.create("server", ServerOptions::class.java)`. That gives `server { ... }` nested inside `greeting { ... }`. Second — the more idiomatic managed approach — declare a `@Nested abstract val server: ServerOptions` getter on the extension; Gradle's `ObjectFactory` materializes it (the same machinery that creates the extension and its `Property` fields), and the build script can configure it with `server { ... }` because the DSL treats the nested managed object as a configurable block. For collections of named sub-objects you use a `NamedDomainObjectContainer` created via `objects.domainObjectContainer(...)`. The deep modeling rules (convention defaults, lazy wiring) belong to the extension-model topic; here the point is just *how the nested DSL syntax is produced*.
code
kotlin · 10 linesabstract class GreetingExtension {
@get:Nested
abstract val server: ServerOptions
fun server(action: Action<in ServerOptions>) = action.execute(server)
}
abstract class ServerOptions {
abstract val url: Property<String>
}
// build.gradle.kts:
// greeting { server { url.set("https://example.com") } }go deeper
Know that nested blocks exist; recognizing greeting { server { } } is configuration nesting is enough.
Explain both child-extension (ExtensionAware) and @Nested managed-getter approaches and that ObjectFactory materializes the latter.
Choose between @Nested, child extension, and NamedDomainObjectContainer based on fixed vs dynamic vs many, and keep fields lazy.
Define DSL ergonomics conventions for plugin suites so nesting is consistent and forward-compatible across the org.
## Why nesting matters Flat extensions get unwieldy. Users expect to group related settings: `greeting { server { url = "..." }; retries = 3 }`. Gradle offers two mechanisms to produce that nested syntax. ## Approach 1 — child extension (ExtensionAware composition) An extension created with `extensions.create()` is itself `ExtensionAware`. So you can attach a child extension to it, exactly like you attach one to the project: ```kotlin val greeting = project.extensions.create("greeting", GreetingExtension::class.java) greeting.extensions.create("server", ServerOptions::class.java) // build script: greeting { server { url = "x" } } ``` The child appears as a nested DSL block named after the string you registered. This is fully dynamic and useful when the nesting is decided at runtime. ## Approach 2 — @Nested managed property (idiomatic) Declare the nested object as a managed getter and let Gradle's `ObjectFactory` instantiate it: ```kotlin abstract class GreetingExtension { @get:Nested abstract val server: ServerOptions fun server(action: Action<in ServerOptions>) = action.execute(server) } abstract class ServerOptions { abstract val url: Property<String> } ``` Gradle creates the `GreetingExtension` via `ObjectFactory`, which also realizes the abstract `@Nested` property as a managed instance. The provided `server(Action)` method (or the auto-generated DSL support) lets the script write `server { url.set("...") }`. `@Nested` additionally tells Gradle to treat the sub-object's inputs as task inputs when the extension feeds a task. ## Approach 3 — containers for many named sub-objects For `servers { prod { ... }; dev { ... } }` you expose a `NamedDomainObjectContainer<ServerOptions>` created with `project.objects.domainObjectContainer(ServerOptions::class.java)`. Each named entry becomes its own block. ## Choosing - Fixed, known sub-block → managed `@Nested` getter (cleanest, supports lazy `Property`). - Dynamically decided sub-block → child extension via `extensions.create()` on the extension. - Arbitrary number of named entries → `NamedDomainObjectContainer`. Note: deep rules about `Property.convention()` defaults and full managed-type semantics live in the extension-model topic; the focus here is producing the nested *DSL shape*.
- What creates the managed @Nested sub-object instance?Gradle's ObjectFactory, the same service that instantiates the extension itself and its abstract Property getters.
- How would you support an arbitrary number of named sub-blocks?Expose a NamedDomainObjectContainer created via objects.domainObjectContainer(Type::class.java); each named entry becomes its own block.
- Why does a child extension nest automatically?Because an extension created with extensions.create() is itself ExtensionAware, so registering a child on it adds a nested DSL block.
saying these in an interview costs you the question
- Saying you must hand-write a closure-handling method and new the nested object manually — ObjectFactory handles managed @Nested objects.
- Confusing @Nested (sub-object) with @Input/@InputFiles task-property annotations.
- Forgetting that extensions are ExtensionAware and thus can host child extensions.