During which build phase does the cost of eager task configuration occur, and how does the Configuration Avoidance API reduce that cost?
answer
- configuration phase runs every build
- tasks.register defers configuration action
- TaskProvider, realized only when needed
- unused tasks never configured
- saving is config-phase, not execution
basics
~10 sThe configuration phase runs every build and configures all tasks. Configuration Avoidance (tasks.register) defers a task's configuration until something actually needs it, so unused tasks are never configured.
solid answer
~40 sGradle runs three phases each invocation: initialization, configuration, then execution. The configuration phase always runs for the whole build and builds the task graph — historically `tasks.create` eagerly configured every task here, even tasks not part of the requested work. The Configuration Avoidance API (`tasks.register`) returns a `TaskProvider` and defers the configuration action until the task is *realized* — i.e. actually needed for the requested graph. So for `./gradlew compileJava`, test/javadoc/etc. tasks registered lazily are never configured, cutting configuration-phase time. The key insight: this is a *configuration-phase* optimization — execution is unaffected. It pays off most in large multi-project builds where eager configuration of hundreds of unused tasks dominated wall-clock time before the build even started running.
code
kotlin · 10 lines// Eager: configures during configuration phase even if never run
tasks.create("slowReport") { doLast { generate() } }
// Lazy: configuration block runs only if slowReport enters the task graph
val slowReport = tasks.register("slowReport") {
doLast { generate() }
}
// named() defers extra configuration of an existing task too
tasks.named("jar") { archiveBaseName.set("app") }go deeper
Know the three phases in order and that tasks.register defers configuration so unused tasks aren't configured.
Explain that configuration runs every build for the whole project graph, so deferring unused tasks' configuration is a real startup saving.
Tie it to TaskProvider/realization, note .get() defeats it, and distinguish it from the Configuration Cache.
Frame it as a build-startup performance lever across a large multi-project codebase and a prerequisite discipline for adopting the Configuration Cache.
## The three lifecycle phases Every Gradle invocation runs three phases in order: 1. **Initialization** — Gradle decides which projects participate (reads `settings.gradle(.kts)`) and creates a `Project` object for each. 2. **Configuration** — Gradle executes the build scripts of *all* participating projects to build the **task graph** (a DAG of `Task` objects and their dependencies). This phase runs **every build**, regardless of which task you asked for. 3. **Execution** — Gradle runs the subset of tasks needed for the requested task(s), respecting up-to-date checks and the build cache. The critical fact: **configuration runs for the whole build every time**, even `./gradlew help`. So any work done while *defining* tasks is paid on every invocation. ## The eager problem The legacy `tasks.create("foo") { ... }` API both **creates** and **configures** the task immediately during the configuration phase. In a build with hundreds of tasks (plus those contributed by plugins), this meant configuring every task — resolving its inputs/outputs, running its configuration closures — even though a typical command only executes a handful. This is wasted configuration-phase work. ## Configuration Avoidance The Configuration Avoidance API replaces eager creation with **lazy registration**: - `tasks.register("foo", Foo::class) { ... }` returns a `TaskProvider<Foo>` and does **not** run the configuration action immediately. - `tasks.named("bar") { ... }` looks up an existing task lazily, also returning a `TaskProvider` and deferring the extra configuration. - The task is only **realized** (created and configured) when something forces it — being part of the requested task graph, or a call that needs the concrete `Task` (e.g. `.get()`). If a registered task is never needed for the requested work, its configuration action never runs. That is the saving — measured in the configuration phase. ```kotlin // Lazy: configuration block runs only if 'report' ends up in the graph val report = tasks.register<Report>("report") { description = "Generates the report" outputDir.set(layout.buildDirectory.dir("reports")) } ``` ## Why it interacts with the phase model Because the saving is purely a configuration-phase concern, it shows up as faster *startup* before any task runs. Pair it with **lazy `Provider`/`Property`** values so even the wiring between tasks (inputs/outputs) is computed lazily, and avoid `.get()` during configuration — calling it forces realization and defeats avoidance. Note: the Configuration Cache is a separate, complementary feature that *stores* the configured task graph to skip the configuration phase entirely on subsequent runs; configuration avoidance reduces the cost *within* a configuration phase that still runs.
- Does configuration avoidance speed up the execution phase?No — execution runs the same tasks either way. The saving is entirely in the configuration phase, by skipping configuration of tasks that aren't part of the requested graph.
- What does it mean for a registered task to be 'realized'?Realization is when Gradle actually creates the Task instance and runs its deferred configuration action — triggered by the task entering the requested graph or by code that needs the concrete Task (e.g. .get()).
Eager creation is cooking every dish on the menu before anyone orders; configuration avoidance is prepping a dish only once it's actually ordered.
saying these in an interview costs you the question
- Claiming the configuration phase only runs for the tasks you requested (it runs for the whole build, every time).
- Saying configuration avoidance speeds up execution rather than the configuration phase.