skip to content

A team's configuration phase is slow. They use tasks.getByName(), withType().all{}, and iterate tasks eagerly to configure them. How would you diagnose and fix this using lazy task-location APIs?

level: seniorimportance: should knowfreq 45%

answer

  1. eager accessors realize unused tasks
  2. named / register / configureEach swaps
  3. build scan / --profile to measure
  4. wire deps via providers, avoid .get()
  5. centralize in convention plugins

basics

~10 s

These eager accessors realize every referenced task during configuration, even unused ones. Replace getByName() with named(), withType().all{} with withType().configureEach{}, and remove eager iteration. Measure with a build scan or --profile before and after.

solid answer

~40 s

The slowness comes from **eager realization**: `getByName()`, `withType().all{}`, and iterating `tasks` (`forEach`/`each`) all force tasks to be created and configured during the configuration phase, including tasks the current invocation will never execute. To diagnose, run a **build scan** (`--scan`) or `--profile` and look at configuration-phase time and the number of realized tasks; a build scan's performance section flags configuration-avoidance opportunities. The fix is mechanical: replace `getByName(name)` with `named(name)`; replace `withType<T>().all { }` with `withType<T>().configureEach { }`; replace eager `tasks.forEach { }` with `tasks.configureEach { }` or a `withType/matching` + `configureEach` selector; and replace `create()` with `register()` for any task creation in scope. Push shared configuration into convention plugins that use these lazy APIs so the whole monorepo benefits. Re-measure to confirm fewer realized tasks and a shorter configuration phase.

code

kotlin · 8 lines
kotlin
// Lazy refactor
tasks.named("jar")
tasks.withType<Test>().configureEach { useJUnitPlatform() }
tasks.configureEach { group = "build" }

// Wire dependency via provider, not realized task
val generate = tasks.register("generate")
tasks.named("compileJava") { dependsOn(generate) }

go deeper

for a junior

Recognize the lazy equivalents (named, configureEach) even if not the full diagnosis.

for a middle

Map each eager accessor to its lazy replacement and apply the refactor correctly.

for a senior

Diagnose with a build scan/--profile, fix provider wiring, and verify reduced realizations.

for a principal

Drive a monorepo-wide strategy via convention plugins and guardrails preventing reintroduction of eager APIs.

## Diagnosis: spotting eager realization Gradle's configuration phase runs *every* build script on *every* invocation. Anything that **realizes** a task there — creating it and running its configuration — costs time even for `./gradlew help`. The eager offenders: - `tasks.getByName('x')` / `tasks['x']` — realizes one task. - `tasks.create('x')` — eagerly creates (use `register`). - `tasks.withType<T>().all { }` — realizes *every* task of type T. - `tasks.forEach { }` / `for (t in tasks)` — realizes the whole container. - `tasks.getByName('x').dependsOn(...)` patterns in cross-project wiring. To measure, use a **build scan** (`./gradlew build --scan`) and inspect the Performance → Configuration section, or `--profile` for an HTML report. Compare configuration time and how many tasks were created vs. how many ran. ## The lazy replacements | Eager | Lazy | |-------|------| | `getByName('x')` | `named('x')` | | `create('x')` | `register('x')` | | `withType<T>().all { }` | `withType<T>().configureEach { }` | | `tasks.forEach { }` | `tasks.configureEach { }` | ```kotlin // Before — eager tasks.getByName("jar") tasks.withType<Test>().all { useJUnitPlatform() } tasks.forEach { it.group = "build" } // After — lazy tasks.named("jar") tasks.withType<Test>().configureEach { useJUnitPlatform() } tasks.configureEach { group = "build" } ``` ## Watch the chain Laziness is contagious in both directions. Even one eager call can cascade: `tasks.named("x").get()` realizes the task; passing `tasks.getByName("y")` as a dependency realizes `y`. Wire dependencies with **providers** (`dependsOn(someTaskProvider)`) rather than realized tasks, and avoid `.get()` on providers during configuration. ## Scale it via convention plugins In a monorepo, the highest-leverage fix is to move the cross-cutting `configureEach` blocks into **convention plugins** applied to all subprojects, so every project gets lazy configuration consistently and no subproject reintroduces eager iteration. ## Verify Re-run with `--scan`/`--profile`; success = fewer realized tasks and a shorter configuration phase, with no behavior change to the tasks that actually execute. ## Summary Eager accessors realize unused tasks; swap them for `named`/`register`/`configureEach`, wire deps via providers, centralize in convention plugins, and confirm with a build scan.

  • How do you confirm the refactor actually helped?
    Run with --scan or --profile before and after; compare configuration-phase time and the count of realized tasks. Fewer realizations and shorter configuration time = success.
  • Why can a single .get() on a TaskProvider undermine avoidance?
    Calling get() during configuration forces the task to realize immediately, the same as getByName(); it cascades to dependencies and defeats laziness.
  • Where should shared lazy configuration live in a monorepo?
    In convention plugins applied across subprojects, so every project gets consistent lazy configuration and none reintroduces eager iteration.

saying these in an interview costs you the question

  • Suggesting --parallel or build cache as the fix for a slow configuration phase (those help execution, not configuration realization).
  • Replacing all() with configureEach but still calling .get() on providers during configuration.
  • Claiming eager iteration is fine because the tasks 'would run anyway' (most referenced tasks do not run).

context