Why prefer configureEach over all (or the eager withType closure) when configuring many tasks?
answer
- all = eager, fires on add
- withType{} closure = eager
- configureEach = lazy
- covers future tasks too
- watch cascading realization
basics
~10 sall and the withType { } closure realize every matching task immediately. configureEach defers configuration so each task is only configured when (and if) it's realized.
solid answer
~40 s`tasks.all { ... }` and the closure overload `tasks.withType(Type) { ... }` are **eager**: they force Gradle to instantiate and configure every matching task right now, defeating configuration avoidance. `tasks.configureEach { ... }` (and `tasks.withType(Type).configureEach { ... }`) is **lazy**: the block is recorded and applied to each matching task only when that task is actually realized, and it also covers tasks registered later. So if you say `tasks.withType<Test>().configureEach { maxParallelForks = 4 }` and never run any test task, no Test task is ever created. The semantics are otherwise the same — applied to current and future matching tasks — but `configureEach` preserves laziness. Inside the block, avoid calling other eager APIs (`getByName`, iterating `tasks`) that would cascade realization.
code
kotlin · 5 lines// EAGER: realizes every JavaCompile in every module, every build
tasks.withType<JavaCompile> { options.encoding = "UTF-8" }
// LAZY: applied only as each JavaCompile is realized
tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" }go deeper
Know that configureEach is the lazy version of all/withType-closure.
Explain that all fires on registration and realizes tasks, while configureEach defers to realization time, and both cover future tasks.
Discuss cascading realization and how plugin authors keep configuration blocks provider-based.
Define org conventions to ban eager collection methods in shared plugins and measure config-time impact.
## The trap: eager collection methods Gradle's `TaskCollection` offers methods that *look* harmless but realize tasks: - `tasks.all { ... }` — runs the action against **every existing and future task, immediately as each is added**. Because it fires on registration, it realizes tasks you may never run. - `tasks.withType(Type) { ... }` (closure overload) — same eager behavior, filtered by type. - `tasks.getByName("x")` — realizes that one task now. ## The lazy replacements - `tasks.configureEach { ... }` — registers an action applied to each task **only when it is realized**, current and future. No realization is forced. - `tasks.withType(Type).configureEach { ... }` — the lazy, type-filtered form. `withType(Type)` returns a live `TaskCollection`; calling `.configureEach` on it keeps everything lazy. - `tasks.named("x") { ... }` — lazy single-task configuration. ## Why the difference is observable Consider a plugin that does `tasks.withType<JavaCompile> { options.encoding = "UTF-8" }`. The closure overload realizes every `JavaCompile` task in every module on every build — even `./gradlew help`. Switching to `.configureEach` means those compile tasks stay unrealized unless a build actually needs to compile. ## Cascading realization Laziness is fragile. If inside a `configureEach` you do something that queries another task eagerly — `project.tasks.getByName(...)`, `someProvider.get()` at configuration time, or iterate the collection — you can trigger a chain of realizations. Keep configuration blocks side-effect-free with respect to other tasks, and wire dependencies with providers (`dependsOn(otherProvider)`). ## Migration heuristic | Eager | Lazy replacement | |---|---| | `tasks.create(...)` | `tasks.register(...)` | | `tasks.getByName("x")` | `tasks.named("x")` | | `tasks.all { }` | `tasks.configureEach { }` | | `tasks.withType(T) { }` | `tasks.withType(T).configureEach { }` | ```kotlin // Eager — realizes every Test task always tasks.withType<Test> { useJUnitPlatform() } // Lazy — only when a Test task is realized tasks.withType<Test>().configureEach { useJUnitPlatform() } ```
- Does configureEach apply to tasks registered after the call?Yes. Like all, configureEach covers both existing and future matching tasks — that's why it's safe to call early in a plugin. The difference is purely the timing of realization, not the set of tasks covered.
- How might a configureEach block accidentally become eager?By querying another task eagerly inside it — e.g. tasks.getByName, provider.get(), or iterating the collection — which forces realization and can cascade across the graph. Wire dependencies with providers instead.
saying these in an interview costs you the question
- Saying all and configureEach differ in which tasks they cover — they cover the same set; only realization timing differs.
- Using getByName/get() inside configureEach without realizing it forces eager realization.