skip to content

@Nested Nested Extensions

Building richer DSLs with @Nested sub-extensions, action-based methods, and NamedDomainObjectContainer for named collections. Interviewers ask about containers because that is how blocks like sourceSets actually work.

on this pageshow

questions

6

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

open as a page

When building a nested extension, why do you create the nested object with ObjectFactory.newInstance and declare its fields as abstract Property getters instead of plain fields with a constructor?

level: middleimportance: must knowfreq 50%

basics

~10 s

ObjectFactory.newInstance makes Gradle create a managed object: it injects services and materializes the abstract Property getters for you, keeping values lazy and wirable into tasks. A plain constructor gives none of that.

open as a page

What does the `@Nested` annotation do in Gradle, and where do you apply it?

level: middleimportance: should knowfreq 45%

basics

~10 s

@Nested marks a getter whose returned object holds further input/output-annotated properties. Gradle walks into that object and treats its annotated members as part of the task's inputs for up-to-date and caching checks.

open as a page

Gradle objects like Project and tasks are ExtensionAware. How do you add your own nested extension to another extension to build a richer, deeply-nested DSL?

level: seniorimportance: should knowfreq 30%

basics

~10 s

If a type implements ExtensionAware, you call theObject.extensions.create("name", Type::class.java) to attach a sub-extension to it. Making your own extension ExtensionAware lets third parties nest their own blocks under yours.

open as a page

When designing a plugin DSL, when should you model configuration as a nested sub-extension versus flat properties on the top-level extension? What are the trade-offs?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Use nesting to group cohesive, related settings (one feature area) into a typed block for discoverability and reuse. Keep things flat when there are only a few settings or no clear grouping. Don't over-nest — deep DSLs are hard to navigate.

open as a page

How does Gradle treat a `@Nested` collection (e.g. a `List` of bean specs) for up-to-date checks, and why does element identity matter?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Gradle fingerprints every element of a @Nested collection by reading each element's input annotations. Element identity (list index, or Named name) determines how changes — including reordering — affect the task's fingerprint and cache key.

open as a page