skip to content

Why prefer configureEach over all (or the eager withType closure) when configuring many tasks?

level: middleimportance: must knowfreq 55%

answer

  1. all = eager, fires on add
  2. withType{} closure = eager
  3. configureEach = lazy
  4. covers future tasks too
  5. watch cascading realization

basics

~10 s

all 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
kotlin
// 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

for a junior

Know that configureEach is the lazy version of all/withType-closure.

for a middle

Explain that all fires on registration and realizes tasks, while configureEach defers to realization time, and both cover future tasks.

for a senior

Discuss cascading realization and how plugin authors keep configuration blocks provider-based.

for a principal

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.

context