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?
answer
- ExtensionAware -> getExtensions()
- extensions.create instantiates + registers
- make extension ExtensionAware for open nesting
- abstract managed types are ExtensionAware
- Convention object is deprecated
basics
~10 sIf 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.
solid answer
~40 s`ExtensionAware` is the interface that exposes an `ExtensionContainer` via `getExtensions()`. `Project`, tasks, and many built-in objects implement it, which is why `extensions.create(...)` works. To build a nested DSL you can either (a) hold a typed nested object directly on your extension (compile-time nesting), or (b) make your extension itself `ExtensionAware` so other plugins can `myExtension.extensions.create("extra", ExtraSpec::class.java)` and contribute *their own* blocks under yours at runtime — the open extensibility model used by `java`, `publishing`, etc. Abstract managed extension classes are `ExtensionAware` automatically. Use compile-time nesting for your own first-party structure; use the ExtensionContainer route when you want an open extension point others can plug into.
code
kotlin · 13 lines// Plugin A defines the parent
val my = project.extensions.create("my", MyExtension::class.java)
// Plugin B nests its own block under it at runtime
(my as ExtensionAware).extensions.create("telemetry", TelemetrySpec::class.java)
// build script
configure<MyExtension> {
// contributed by plugin B
(this as ExtensionAware).extensions.configure<TelemetrySpec>("telemetry") {
enabled.set(true)
}
}go deeper
Know that extensions.create adds a block and that Project supports it.
Distinguish extensions.create vs add vs getByType and create a simple nested object.
Design an open extension point: make your extension ExtensionAware so other plugins can nest under it; avoid Convention.
Govern the org's plugin extension-point strategy — stable public surfaces, versioned nested DSLs, and migration off Convention.
## ExtensionAware and the ExtensionContainer `org.gradle.api.plugins.ExtensionAware` declares one method: `ExtensionContainer getExtensions()`. The `ExtensionContainer` is a registry of named, typed objects. `extensions.create("java", JavaPluginExtension::class.java)` both instantiates the extension (via ObjectFactory) and registers it so the DSL block `java { }` resolves to it. `extensions.add(...)` registers an existing instance; `extensions.getByType(...)` / `findByName(...)` retrieve them. ## Two flavours of nesting **1. Closed / compile-time nesting.** Your extension owns a strongly-typed nested object as a property (an abstract getter or an ObjectFactory-created instance). You control the whole shape; users configure inner blocks you defined. This is the common case and is fully typed. **2. Open / runtime nesting via ExtensionAware.** If your extension type implements `ExtensionAware` (abstract managed types do by default), other plugins can attach *their* extensions to it: ```kotlin val my = project.extensions.create("my", MyExtension::class.java) (my as ExtensionAware).extensions.create("telemetry", TelemetrySpec::class.java) // users: my { telemetry { enabled.set(true) } } ``` This is how Gradle lets ecosystems extend a block they don't own — the way `publishing { }` accepts `repositories { }` and plugin-contributed publication types. ## Convention vs extension (historical note) Older plugins used the *Convention* object (also reachable via `ExtensionAware`-adjacent `Convention`) to attach behaviour. Conventions are deprecated/removed in modern Gradle; the `ExtensionContainer` is the supported path. Don't introduce new `Convention`-based nesting. ## Choosing an approach - First-party, fixed structure -> compile-time nested object (typed, simplest). - Public extension point for third parties -> make it `ExtensionAware` and let them `extensions.create` under it. - Keep nested objects managed (`Property`, `newInstance`) so values stay lazy and wire into tasks cleanly. ## Retrieval From anywhere you can navigate: `project.extensions.getByType(MyExtension::class.java)`, and for a nested ExtensionAware, `(my as ExtensionAware).extensions.getByName("telemetry")`.
- What is the difference between compile-time nested extensions and ExtensionAware-based nesting?Compile-time nesting puts a typed nested object on your extension that you fully control. ExtensionAware-based nesting opens your extension so other plugins can register their own sub-extensions on it at runtime — an open extension point versus a closed structure.
- Why avoid the Convention object for new plugins?Conventions are deprecated and being removed; the supported mechanism is the ExtensionContainer. New code should attach behaviour and DSL via extensions, not conventions.
saying these in an interview costs you the question
- Recommending the legacy Convention object for new nested DSLs.
- Assuming every object is ExtensionAware — only objects implementing the interface (Project, tasks, managed extensions, etc.) expose getExtensions().