What does the tasks { } block do in the Kotlin DSL, and what is the eager-realization trap when you reference a task by accessor like tasks.test inside it?
answer
- tasks { } = TaskContainer receiver, sugar only
- generated accessors realize eagerly
- test { } ≈ getByName
- named<Test>("test") stays lazy
- registering/existing = lazy delegates
basics
~10 stasks { } gives you the TaskContainer as receiver so you can call register/named without the tasks. prefix. Referencing a typed accessor like tasks.test realizes that task eagerly.
solid answer
~40 s`tasks { ... }` is just a scoping block whose receiver is the project's `TaskContainer`; inside it you can write `register<Jar>("x")` or `named<Test>("test") { }` without repeating `tasks.`. It does **not** by itself make anything eager. The trap is the **type-safe task accessors** the Kotlin DSL generates, such as `tasks.test`, `tasks.jar`, or `val test by tasks.existing(Test::class)` used carelessly: dereferencing a generated accessor like `tasks.test { }` *realizes* that task immediately, just like `getByName`. So inside `tasks { }`, prefer `named<Test>("test") { }` over the `test { }` accessor when you care about configuration avoidance. The block is convenience; the eagerness comes from how you reference the task, not from the block.
code
kotlin · 11 linestasks {
// EAGER: the generated 'test' accessor realizes the task
// test { useJUnitPlatform() }
// LAZY: deferred configuration
named<Test>("test") { useJUnitPlatform() }
val dist by registering(Zip::class) {
archiveFileName.set("dist.zip")
}
}go deeper
Know tasks { } is shorthand and that you register/configure tasks inside it.
Explain that generated accessors realize eagerly while named/registering stay lazy.
Coach teams to avoid bare accessors in convention plugins and prefer the lazy delegates.
Set conventions and reviews that forbid eager accessors in shared build logic and quantify configuration-time impact.
## What the block is In the Kotlin DSL, `tasks { }` is an overload that takes a lambda with the `TaskContainer` as its receiver. It exists purely to reduce noise: ```kotlin tasks { register<Zip>("dist") { /* ... */ } named<Test>("test") { useJUnitPlatform() } } ``` is equivalent to calling `tasks.register(...)` and `tasks.named(...)` outside the block. The block changes *nothing* about laziness on its own. ## Where eagerness sneaks in: generated accessors The Kotlin DSL generates **type-safe accessors** for tasks contributed by applied plugins. With the `java` plugin you can write `tasks.test`, `tasks.jar`, `tasks.compileJava`. These accessors are convenient but **eager** — referencing one realizes the task. So: ```kotlin tasks { test { useJUnitPlatform() } // 'test' accessor → realizes the Test task NOW } ``` realizes `test` during configuration even if you never run tests. The lazy equivalent is: ```kotlin tasks { named<Test>("test") { useJUnitPlatform() } // deferred } ``` ## Why the accessor is eager Generated accessors return the realized task (or call into `getByName`-style lookups) so they can offer a typed instance directly. That immediacy is exactly what defeats configuration avoidance. `named<Test>("test")` instead returns a `TaskProvider<Test>` and defers the work. ## The `existing`/`registering` delegates Kotlin DSL also offers property-delegate forms: ```kotlin val dist by tasks.registering(Zip::class) { /* lazy register */ } val test by tasks.existing(Test::class) // lazy provider to an existing task ``` `registering` is lazy (like `register`) and `existing` returns a lazy provider (like `named`). These are the lazy delegate counterparts; the eager counterpart `tasks.creating`/direct accessor realizes immediately. ## Practical guidance - Use `tasks { }` freely — it's just sugar. - Inside it, reach tasks via `register`/`named`/`registering`/`existing`. - Avoid the bare generated accessors (`test { }`, `jar { }`) in performance-sensitive build logic, because they realize. - If you must use an accessor for brevity in a small build, understand it's the same cost as `getByName`.
- Does wrapping configuration in tasks { } make it lazy?No. The block only sets the TaskContainer as receiver. Laziness depends on whether you use register/named (lazy) or accessors/getByName (eager).
- What is the lazy equivalent of the generated tasks.test { } accessor?tasks.named<Test>("test") { } — it returns a TaskProvider and defers configuration until the task is realized.
- What do the `registering` and `existing` delegates give you?Lazy property-delegate forms: registering is like register (creates lazily); existing is like named (lazy provider to an existing task).
saying these in an interview costs you the question
- Believing tasks { } itself introduces or removes laziness.
- Treating tasks.test { } as a lazy operation.