skip to content

Explain tasks.withType<Test>().configureEach { } — what does it do, and why use configureEach instead of all() or each()?

level: middleimportance: must knowfreq 60%

answer

  1. withType = live collection by type
  2. configureEach = lazy, once per task
  3. applies to current + future tasks
  4. all()/each() realize eagerly — avoid
  5. matching{} filters by predicate

basics

~10 s

withType<Test>() selects all tasks of type Test, and configureEach applies the block to each — both existing and future ones — lazily. configureEach defers configuration; all()/each() would eagerly realize every matching task.

solid answer

~40 s

`tasks.withType<Test>()` returns a live, filtered `TaskCollection` of every task whose type is (or extends) `Test`. Calling `.configureEach { maxParallelForks = 4 }` registers a configuration action that runs **lazily and once per task**, applying to tasks already declared *and* any registered later. Crucially, `configureEach` does **not** realize the tasks — each block runs only if/when that task is actually realized. The older `withType(Test).all { ... }` (or iterating with `each`/`forEach`) instead realizes *every* matching task immediately, defeating configuration avoidance. So `withType(...).configureEach { }` is the idiomatic way to apply cross-cutting configuration to a whole task type — e.g. setting JVM args on all `Test` tasks or output options on all `JavaCompile` tasks — without paying to create tasks the current invocation won't run.

code

kotlin · 9 lines
kotlin
tasks.withType<Test>().configureEach {
    useJUnitPlatform()
    maxParallelForks = 4
}

// Avoid: realizes every Test task now
tasks.withType<Test>().all {
    useJUnitPlatform()
}

go deeper

for a junior

Recognize withType().configureEach{} as the way to configure all tasks of a type; basic usage is enough.

for a middle

Explain the live-collection + lazy-once-per-element semantics and why configureEach beats all()/iteration.

for a senior

Discuss composing withType().configureEach with named(), and the avoidance trade-offs of matching{} vs withType.

for a principal

Define build-convention patterns (plugins applying configureEach) and measure configuration-time impact of eliminating eager iteration across a monorepo.

## withType: selecting tasks by type `tasks.withType<Test>()` (Kotlin) / `tasks.withType(Test)` (Groovy) returns a **live `TaskCollection<Test>`** containing every task whose type is `Test` or a subtype. "Live" means the collection automatically reflects tasks registered before *and after* the call. This is the primary way to apply configuration to a *category* of tasks rather than one named task. ## configureEach: lazy, once-per-element `configureEach { }` schedules an action that Gradle runs **once per element, lazily** — only when each task is realized. It applies to current members of the collection and to any added later. Because it never forces realization, it is fully compatible with configuration avoidance: ```kotlin tasks.withType<Test>().configureEach { useJUnitPlatform() maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) jvmArgs("-XX:+UseParallelGC") } ``` None of the `Test` tasks are created by this block; the action fires when (and if) a given `Test` task is realized. ## Why not all() / each() / iteration `withType(Test).all { }` and any eager iteration (`forEach`, `each`, `for (t in tasks.withType<Test>())`) **realize every matching task immediately**, running their full configuration during the configuration phase even for tasks that won't execute. On large builds this is the classic configuration-avoidance regression. `all { }` also runs for future additions but still eagerly. Prefer `configureEach`. ## Combining with named() `withType` answers "configure all tasks of this type"; `named()` answers "configure this one task". They compose: bulk-configure with `withType(...).configureEach`, then tweak a specific one with `named(...)`. ## matching: filtering by predicate For selection by an arbitrary condition rather than type, `tasks.matching { it.name.startsWith("generate") }` returns a filtered live collection you can again `.configureEach { }`. Note `matching` evaluates its predicate, so it is less avoidance-friendly than `withType` (which filters purely by type metadata) — prefer `withType` when a type filter suffices. ## Summary - `withType<T>()` → live collection of type T tasks. - `.configureEach { }` → lazy, once-per-task, current + future, no realization. - `.all { }` / iteration → eager, realizes everything — avoid.

  • Does configureEach apply to tasks registered after the configureEach call?
    Yes — withType returns a live collection, so configureEach's action also fires for matching tasks added later.
  • Why is withType().configureEach{} more avoidance-friendly than matching{}.configureEach{}?
    withType filters by type metadata without realizing tasks; matching{} must evaluate a predicate against tasks, which can be less lazy. Prefer withType when a type filter is enough.
  • When would all() ever be acceptable?
    Almost never in modern builds; only legacy code or cases where you truly must realize every matching task immediately, which is rare and usually a smell.

saying these in an interview costs you the question

  • Saying configureEach only affects already-existing tasks (it also affects future ones).
  • Treating all() and configureEach as equivalent in performance.
  • Claiming configureEach realizes the tasks (it defers until realization).

context