Explain how closure delegation works in a Groovy build script — for example, inside `repositories { mavenCentral() }`, where does `mavenCentral()` actually get called?
answer
- closure: this / owner / delegate
- Gradle sets delegate = config object
- resolveStrategy = DELEGATE_FIRST
- mavenCentral() → RepositoryHandler
- fallback to script for ext/helpers
basics
~10 srepositories { ... } passes a closure whose delegate Gradle sets to the RepositoryHandler. Inside the block, mavenCentral() is an unqualified call that the closure routes to its delegate, so it really invokes RepositoryHandler.mavenCentral().
solid answer
~40 sAlmost every Gradle `{ ... }` configuration block is a Groovy **closure**. A closure resolves unqualified method/property calls against an `owner`, a `delegate`, and `this`, in an order controlled by its `resolveStrategy`. Gradle calls the closure with its delegate set to the object being configured and `resolveStrategy = DELEGATE_FIRST`, so calls like `mavenCentral()`, `gradlePluginPortal()`, or `implementation(...)` are dispatched to that delegate (`RepositoryHandler`, `DependencyHandler`, etc.) before falling back to the enclosing script. This is what makes the nested DSL read declaratively without you naming the receiver each line. The same mechanism powers `dependencies { }`, `tasks.test { }`, and extension blocks contributed by plugins. The trade-off is, again, dynamic: an unknown method inside the block only fails at runtime via `methodMissing`/`MissingMethodException`, not at compile time.
code
groovy · 5 lines// Equivalent to what Gradle does under the hood:
def cfg = { mavenCentral() }
cfg.delegate = theRepositoryHandler
cfg.resolveStrategy = Closure.DELEGATE_FIRST
cfg() // -> theRepositoryHandler.mavenCentral()go deeper
Know that the braces are a closure and the methods inside act on the thing being configured.
Explain owner/delegate/this, resolveStrategy = DELEGATE_FIRST, and that Gradle sets the delegate to the config object.
Discuss the fallback-to-script rationale and contrast with the Kotlin DSL's compile-time accessors.
Use the delegation model to reason about plugin-contributed extensions and to justify DSL ergonomics/migration decisions org-wide.
## Closures have three resolution targets A Groovy closure can resolve an unqualified name (`foo()` or `bar`) against three things: - **`this`** — the enclosing class instance, - **`owner`** — the object that *defined* the closure (usually the script or outer closure), - **`delegate`** — a target object you can set explicitly. The order is governed by `resolveStrategy`: `OWNER_FIRST` (default), `DELEGATE_FIRST`, `DELEGATE_ONLY`, etc. ## What Gradle does When you write: ```groovy repositories { mavenCentral() maven { url 'https://example.com/repo' } } ``` Gradle takes that closure, sets `delegate = <the RepositoryHandler>` and `resolveStrategy = Closure.DELEGATE_FIRST`, then executes it. So `mavenCentral()` is resolved **on the delegate first**, calling `RepositoryHandler.mavenCentral()`. The nested `maven { url ... }` repeats the trick with a `MavenArtifactRepository` delegate, so `url` sets that repository's URL. ## Why DELEGATE_FIRST and not DELEGATE_ONLY Gradle uses DELEGATE_FIRST (not ONLY) so that names not found on the delegate can still fall back to the surrounding script — e.g. referencing an `ext` property or a helper method defined at script scope from inside the block. ## This is the whole DSL Nearly every block — `dependencies { }`, `android { }`, `publishing { }`, `tasks.named('x') { }` — is the same pattern: a closure whose delegate is a configuration object (often a plugin-contributed *extension* on an `ExtensionAware`). Understanding delegation is the single most useful mental model for reading any Groovy Gradle script. ## The cost Because method dispatch is dynamic, a misspelled `mavenCentrl()` inside the block throws `MissingMethodException` at configuration time — there is no compile-time signal. The Kotlin DSL trades this dynamism for statically generated, IDE-completable accessors.
- Why does Gradle use DELEGATE_FIRST rather than DELEGATE_ONLY for configuration closures?So names absent on the delegate can still fall back to the enclosing script — letting you read `ext` properties or call script-level helper methods from inside the block.
- How does the Kotlin DSL achieve the same nested blocks without closure delegation?It uses Kotlin lambdas with receivers and statically generated, type-safe accessors, so `mavenCentral()` is a real method call resolved at compile time with IDE completion.
The block is like a sealed room where, before you start talking, Gradle hands you a 'who to address by default' card (the delegate). Say mavenCentral() and it's understood as 'RepositoryHandler, do this' without you naming the receiver.
saying these in an interview costs you the question
- Saying `mavenCentral()` is a global function — it's a method on the delegate object.
- Confusing `owner` and `delegate`, or claiming the default resolveStrategy in Gradle is OWNER_FIRST (Gradle sets DELEGATE_FIRST).