skip to content

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 = ... } }`?

level: middleimportance: must knowfreq 55%

answer

  1. single-Action method -> name { } block
  2. objectFactory.newInstance, never new
  3. abstract getter -> managed nested
  4. action.execute(nested)
  5. nested type uses Property<T>

basics

~10 s

Add 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 s

Create 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 lines
kotlin
abstract 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

for a junior

Recognize that block { sub { } } is a nested extension and that a method taking an Action enables the inner block.

for a middle

Implement it: separate nested type, ObjectFactory.newInstance, an Action-taking method, lazy Property fields.

for a senior

Prefer abstract managed extensions so Gradle synthesizes the instance + action method; reason about reuse of the nested type.

for a principal

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.

context