What operations during the configuration phase force a lazily-registered task to be realized, defeating configuration avoidance?
answer
- .get() realizes the provider
- getByName / findByName are eager
- tasks.all → configureEach
- withType {} → withType().configureEach {}
- wire by provider, use map/flatMap
basics
~10 sCalling .get() on a TaskProvider, or using eager APIs like tasks.getByName, tasks.all, or withType {} without a lazy variant, forces the task to be created and configured during the configuration phase.
solid answer
~40 sRegistration is only beneficial if you never accidentally **realize** the task during configuration. Realization happens when Gradle is forced to materialize the concrete `Task` and run its deferred configuration. Common triggers: calling `provider.get()`; eager lookups like `tasks.getByName(...)` / `tasks["name"]`; iterating eagerly with `tasks.all { }` or `tasks.withType(Type) { }` *without* the lazy `configureEach` form; and `tasks.getByName` inside another task's config. The lazy-safe alternatives are `tasks.named(...)`, `tasks.withType(Type).configureEach { }`, and passing the `TaskProvider`/`Provider` itself into `dependsOn`/inputs/outputs rather than resolving it. One eager call can cascade: realizing a task whose config touches other providers can realize those too. So avoiding eager accessors and deferring value reads (`Provider.map`/`flatMap` instead of `.get()`) is what preserves the configuration-phase saving.
code
kotlin · 10 lines// EAGER — realizes during configuration, defeats avoidance
tasks.all { group = "custom" }
val jar = tasks.getByName("jar")
// LAZY — preserves configuration avoidance
tasks.configureEach { group = "custom" }
tasks.named("jar") { /* configured only if realized */ }
// transform output without realizing
val out = tasks.named<Jar>("jar").flatMap { it.archiveFile }go deeper
Know that calling .get() or getByName forces the task to be created right away.
List the eager triggers (get/getByName/all/withType{}) and their lazy replacements (named/configureEach), and wire dependencies by provider.
Explain cascading realization and use map/flatMap to keep value reads lazy; audit convention plugins for stray eager calls.
Establish team conventions and CI checks that forbid eager accessors in shared/convention plugins to protect configuration-phase performance at scale.
## Realization: the moment avoidance can be lost `tasks.register(...)` returns a `TaskProvider<T>` and stores a deferred configuration action. **Realization** is when Gradle actually instantiates the `Task` and runs that action. This normally happens lazily — only when the task is part of the requested graph. But several APIs force it *eagerly during configuration*, which silently undoes the benefit. ## Eager triggers to avoid - **`taskProvider.get()`** — directly materializes the task. Use `.map { }` / `.flatMap { }` to transform without realizing, or pass the provider where a `Provider` is accepted. - **`tasks.getByName("x")`, `tasks["x"]`, `tasks.findByName("x")`** — eager lookups that realize the task. Replace with `tasks.named("x")` which returns a provider. - **`tasks.all { }`** — runs the action against every task *and every future task*, realizing them all. Replace with **`tasks.configureEach { }`**. - **`tasks.withType(T) { }` (the eager overload)** — realizes matching tasks. Use **`tasks.withType(T).configureEach { }`**. - **`tasks.create(...)`** — eager by definition; prefer `register`. ## Lazy-safe wiring The goal is to wire dependencies and inputs/outputs **by provider**, so nothing is realized until execution-graph computation needs it: ```kotlin val generate = tasks.register<Generate>("generate") { outputFile.set(layout.buildDirectory.file("gen/out.txt")) } tasks.named<JavaCompile>("compileJava") { // pass the provider, do NOT call generate.get() inputs.file(generate.flatMap { it.outputFile }) dependsOn(generate) } ``` Here `dependsOn(generate)` accepts the `TaskProvider` directly; `flatMap` transforms the output location lazily. No `.get()` is called, so `generate` is realized only if `compileJava` actually runs. ## Cascading realization Realization can cascade: if you realize task A and A's configuration reads `taskB.get()`, B is realized too. In big plugin ecosystems a single eager accessor in a convention plugin can realize a large subtree. This is why build-scan / `--profile` configuration-time investigations often trace a slowdown to one stray eager call. ## Detecting it Use a build scan or `org.gradle.internal.tasks.stats` style diagnostics, or simply audit for `getByName`, `.all {`, `withType(... ) {` (without `configureEach`), and `.get()` in configuration code. Keeping these out is the discipline that makes registration actually pay off.
- How do you transform a TaskProvider's output without realizing the task?Use Provider.map/flatMap — e.g. provider.flatMap { it.outputFile }. These build a lazy chain; the underlying task is realized only when the resulting value is actually queried during graph computation or execution.
- What's the difference between tasks.all {} and tasks.configureEach {}?tasks.all immediately realizes and configures every existing and future task; tasks.configureEach registers the action lazily so each task's configuration runs only when that task itself is realized.
saying these in an interview costs you the question
- Thinking tasks.register alone guarantees avoidance regardless of how you reference the task afterward.
- Using tasks.withType(T) {} or tasks.all {} in a plugin and assuming it stays lazy.
- Calling .get() in configuration code to read a task's output instead of using flatMap.