skip to content

During which build phase does the cost of eager task configuration occur, and how does the Configuration Avoidance API reduce that cost?

level: juniorimportance: must knowfreq 70%

answer

  1. configuration phase runs every build
  2. tasks.register defers configuration action
  3. TaskProvider, realized only when needed
  4. unused tasks never configured
  5. saving is config-phase, not execution

basics

~10 s

The 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 s

Gradle 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
kotlin
// 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

for a junior

Know the three phases in order and that tasks.register defers configuration so unused tasks aren't configured.

for a middle

Explain that configuration runs every build for the whole project graph, so deferring unused tasks' configuration is a real startup saving.

for a senior

Tie it to TaskProvider/realization, note .get() defeats it, and distinguish it from the Configuration Cache.

for a principal

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.

context