What operations force a lazily-registered task to be realized, and how do you avoid accidentally triggering them?
answer
- get(), getByName, create
- all/withType-closure, iteration
- CLI request + dependsOn chain
- dependsOn(provider) not .get()
- flatMap outputs, --profile/scan
basics
~10 sCalling get() on a TaskProvider, getByName/create, tasks.all/withType-closure, iterating the task container, or requesting the task on the CLI all realize it. Wire dependencies with providers and use named/configureEach to stay lazy.
solid answer
~40 sA registered task is realized when something *demands* the actual task object. Common triggers: calling `provider.get()`, `tasks.getByName(...)`, `tasks.create(...)`, the eager `tasks.all { }` / `tasks.withType(T) { }` closures, iterating or calling `.size` on the task container, or requesting the task on the command line (and its transitive dependencies). The defense is to keep references as `TaskProvider`s end-to-end: declare dependencies with `dependsOn(provider)` (not `dependsOn(provider.get())`), pass task outputs via `Provider`/`flatMap` rather than reading `task.outputs` eagerly, and configure with `named { }` / `configureEach { }`. Build scans and the `--profile` report show configuration time; you can also detect unexpected realization by checking which tasks are configured for a no-op build. The goal is that for any given invocation only the requested tasks and their real dependencies are realized.
code
kotlin · 9 linesval generate = tasks.register<Copy>("generate") {
into(layout.buildDirectory.dir("gen"))
}
tasks.register<Zip>("pack") {
// lazy: provider chained via flatMap, 'generate' realized only if 'pack' runs
from(generate.flatMap { it.destinationDirectory })
dependsOn(generate) // pass the provider, never generate.get()
}go deeper
Recognize that calling get() on a provider realizes the task.
List the common triggers (get, getByName, create, eager collection methods, CLI request).
Show how to keep references lazy with providers/flatMap and diagnose realization via --profile or build scans.
Tie avoidance to configuration-cache readiness and enforce realization-free convention plugins org-wide.
## What "forces realization" Realization happens whenever code needs the concrete task instance rather than a promise of it. The main triggers: 1. **`taskProvider.get()`** — explicitly asks for the task now. 2. **`tasks.getByName(name)` / `tasks.named(name).get()`** — locating and dereferencing eagerly. 3. **`tasks.create(...)`** — eager creation. 4. **Eager collection methods** — `tasks.all { }`, `tasks.withType(T) { }` (closure), and iterating the `TaskContainer` (`for (t in tasks)`, `tasks.toList()`, `tasks.size`). 5. **Command-line request** — naming the task (or any task that `dependsOn` it via a realized path) realizes it and its dependency chain. 6. **`mustRunAfter` / `dependsOn` given a realized task** — passing `provider.get()` instead of the provider. ## Keeping references lazy ```kotlin val jar = tasks.register<Jar>("jar") // GOOD: provider passed through — no realization tasks.register("dist") { dependsOn(jar) } // BAD: get() realizes 'jar' even if 'dist' is never run tasks.register("dist") { dependsOn(jar.get()) } ``` ## Passing outputs without realization Use `Provider` mapping to consume another task's output lazily: ```kotlin val generate = tasks.register<Copy>("generate") { into(layout.buildDirectory.dir("gen")) } tasks.register<Zip>("pack") { // flatMap keeps it a provider; no eager realize of 'generate' from(generate.flatMap { it.destinationDir }) } ``` ## Diagnosing accidental realization - Run `./gradlew help --profile` or open a **build scan**: the *Configuration* section lists tasks configured. For `help`, ideally almost none of your custom tasks appear. - The **configuration cache** (`--configuration-cache`) makes accidental eager work more visible and is the strategic direction; avoidance is a prerequisite for it to pay off. - Add temporary logging in a `configureEach`/`register` block — if it prints for `./gradlew help`, that task was realized unnecessarily. ## Why it matters at scale In a monorepo, one eager `withType` in a convention plugin multiplies across every module, turning a 1s configuration into many seconds on every command — including IDE syncs. Senior/plugin authors treat "does this realize tasks?" as a code-review checklist item.
- Why is dependsOn(provider) lazy but dependsOn(provider.get()) eager?dependsOn accepts a TaskProvider and records the dependency without realizing the task; the dependency is only realized if the depending task is part of the build. Calling .get() forces the task object to be created immediately, at configuration time, regardless of whether it's needed.
- How does the configuration cache relate to configuration avoidance?Configuration avoidance reduces configuration-phase work; the configuration cache stores the result of the configuration phase and skips it entirely on subsequent runs. Avoidance is complementary — fewer realized tasks means a smaller, cleaner cached configuration and fewer cache-incompatibility issues.
saying these in an interview costs you the question
- Thinking dependsOn always realizes the dependency — it doesn't when you pass the provider.
- Reading task.outputs / task.destinationDir directly at configuration time instead of mapping the provider.