How do you give a Gradle extension a nested configuration block (a sub-extension) that users configure with a block in the build script, like `myExtension { server { host = ... } }`?
answer
- single-Action method -> name { } block
- objectFactory.newInstance, never new
- abstract getter -> managed nested
- action.execute(nested)
- nested type uses Property<T>
basics
~10 sAdd a property of the nested type to the extension, then add a method that takes an Action<NestedType> and calls action.execute(nested). Gradle's DSL lets users configure it with a block.
solid answer
~30 sCreate the nested object as its own class (often abstract with `Property`/`ObjectFactory`-managed fields). On the parent extension, hold an instance of it (created via `objectFactory.newInstance(...)`), expose it via a getter, and add a configuration method that accepts `Action<in NestedType>` and invokes `action.execute(nested)`. Gradle turns any single-`Action` method named `server` into a `server { ... }` DSL block automatically. For abstract extension classes Gradle synthesizes these action methods for you, so just declaring an abstract getter returning the nested type is often enough. This gives a clean, typed, nested DSL without writing boilerplate setters.
code
kotlin · 17 linesabstract class ServerSpec {
abstract val host: Property<String>
abstract val port: Property<Int>
}
abstract class MyExtension @Inject constructor(private val objects: ObjectFactory) {
val server: ServerSpec = objects.newInstance(ServerSpec::class.java)
fun server(action: Action<in ServerSpec>) = action.execute(server)
}
// build.gradle.kts
configure<MyExtension> {
server {
host.set("db.local")
port.set(5432)
}
}go deeper
Recognize that block { sub { } } is a nested extension and that a method taking an Action enables the inner block.
Implement it: separate nested type, ObjectFactory.newInstance, an Action-taking method, lazy Property fields.
Prefer abstract managed extensions so Gradle synthesizes the instance + action method; reason about reuse of the nested type.
Define conventions for how teams structure deeply nested plugin DSLs, balancing discoverability, lazy wiring, and compatibility across plugin versions.
## What a nested extension is An *extension* is the object Gradle attaches to a project (or task/other extension-aware object) that backs a configuration block — e.g. the `java { }` block is the `JavaPluginExtension`. A **nested extension** is simply a property on one extension whose value is itself a configuration object, so users can write a block *inside* a block: ``` myExtension { server { host = "db.local" } } ``` ## Two ways to expose the nested block **1. Action-based method (explicit).** Gradle's DSL rule: any method on an extension that takes a single `org.gradle.api.Action<T>` parameter is callable as a `name { ... }` block. So if you write a method `server(Action<? super ServerSpec> action)`, the script can call `server { ... }`. Inside you call `action.execute(server)` against the held instance. **2. Abstract getter (managed).** If your extension is an `abstract class`/interface, Gradle's *managed properties* feature can synthesize both the instance and the action method. Declaring an abstract getter that returns the nested type is enough — Gradle creates the nested object (recursively) and generates the matching `name { ... }` block. ## Why a separate type, not loose properties Grouping related settings into a nested type keeps the top-level extension flat and discoverable, gives IDE auto-completion per block, and lets you reuse the nested type across plugins. The nested object should use `Property<T>`/`ListProperty<T>` so values stay lazy and wire cleanly into tasks. ## Creating instances correctly Never `new ServerSpec()`. Use `ObjectFactory.newInstance(ServerSpec::class.java)` so Gradle does dependency injection and creates managed `Property` instances for abstract fields. ```kotlin abstract class ServerSpec { abstract val host: Property<String> abstract val port: Property<Int> } abstract class MyExtension @Inject constructor(objects: ObjectFactory) { val server: ServerSpec = objects.newInstance(ServerSpec::class.java) fun server(action: Action<in ServerSpec>) = action.execute(server) } ``` With a fully abstract extension you can drop the explicit instance and method and just declare `abstract val server: ServerSpec` — Gradle does the rest.
- What is the DSL rule that makes `server { ... }` work?Any method on the extension taking a single `Action<T>` parameter becomes a `methodName { ... }` configuration block. Gradle creates a closure/lambda-backed Action and executes it against the receiver.
- Why create the nested instance with ObjectFactory rather than a constructor call?So Gradle performs constructor injection and materializes managed `Property`/`ListProperty` fields for abstract members. A raw `new` skips all of that and you lose managed properties and decoration.
saying these in an interview costs you the question
- Saying you must write manual setters for each nested field instead of using managed Property types.
- Instantiating the nested type with a plain constructor instead of ObjectFactory.newInstance.