What happens during Gradle's Execution phase, and how does it differ from the Configuration phase?
answer
- third / last phase
- runs @TaskAction + doFirst/doLast
- config builds objects, exec runs work
- outcome labels UP-TO-DATE / SKIPPED
- command-line tasks + their deps
basics
~20 sExecution is the third (last) phase. Gradle runs the @TaskAction methods of the selected tasks in dependency order. Configuration, the earlier phase, only builds task objects and wires their dependencies — it does no actual work.
solid answer
~30 sA Gradle build runs in three phases: **Initialization** (decide which projects participate), **Configuration** (evaluate every build script, create task objects, wire `dependsOn`/inputs/outputs), and **Execution**. In Execution, Gradle takes the tasks you requested on the command line, builds and topologically orders the task graph, and then runs each selected task's `@TaskAction` methods. Crucially, only the *actions* run here — all the DSL configuration code (`tasks.register { ... }` blocks, `doFirst`/`doLast` registration) ran earlier in Configuration. For each task Gradle reports an outcome: executed, `UP-TO-DATE`, `FROM-CACHE`, `SKIPPED`, or `NO-SOURCE`. If a task action throws, the build fails fast (unless `--continue`).
code
kotlin · 8 linestasks.register("greet") {
// CONFIGURATION time: runs on every build, even if 'greet' isn't requested
println("configuring greet")
doLast {
// EXECUTION time: runs only when 'greet' actually executes
println("Hello from execution!")
}
}go deeper
Name the three phases and state that Execution runs the actions of the requested tasks in dependency order.
Distinguish configuration-time vs execution-time code, and list the outcome labels (UP-TO-DATE, SKIPPED, FROM-CACHE).
Explain how task selection + graph ordering feeds Execution, and why misplacing code in configuration blocks hurts every build.
Relate phase separation to configuration-cache and parallel-execution guarantees, and to enforcing fast configuration across a large multi-project build.
## The three lifecycle phases Every Gradle invocation moves through three phases, strictly in order: 1. **Initialization** — Gradle evaluates `settings.gradle(.kts)` and decides which projects make up the build, creating a `Project` instance for each. 2. **Configuration** — Gradle executes the body of *every* build script (subject to configuration avoidance). This is where task **objects** are created and their wiring is declared: `dependsOn`, `inputs`, `outputs`, `mustRunAfter`. No task *work* happens here. 3. **Execution** — Gradle determines which of the configured tasks to actually run (from the command-line request + their dependencies), orders them, and invokes their actions. ## What runs in Execution A task is a sequence of **actions**. Actions come from: - The `@TaskAction`-annotated method(s) of a typed task class. - `doFirst { }` and `doLast { }` blocks (prepended/appended action lists). During Execution, for each task in graph order, Gradle: 1. Computes the task's **input/output snapshot** and checks up-to-date / build-cache state. 2. If work is needed, runs `doFirst` actions, then `@TaskAction`, then `doLast` actions. 3. Records an **outcome**. ``` Configuration time: tasks.register("hello") { doLast { println("hi") } } // runs in Configuration; the doLast LAMBDA is only REGISTERED Execution time: the registered doLast lambda actually prints "hi" ``` ## Common confusion: code placement Code written directly in a `register`/`configure` block runs at **Configuration** time, on *every* build, even if the task is never executed. Code inside `doLast { }`/`doFirst { }`/`@TaskAction` runs at **Execution** time, only when the task actually runs. Putting expensive logic in the configuration block is a classic mistake that slows every invocation. ## Outcomes you will see - *(no label)* — the action ran. - `UP-TO-DATE` — inputs/outputs unchanged, skipped. - `FROM-CACHE` — outputs pulled from the build cache. - `SKIPPED` — an `onlyIf {}` predicate returned false, or `--exclude-task`. - `NO-SOURCE` — the task had declared sources but they were empty.
- If you run `gradle help`, does the `doLast` block of a `greet` task you defined still run?No. Its configuration block may run (unless avoided via lazy registration), but the `doLast` action only runs if `greet` itself is in the executed task graph.
- Where should expensive computation live to avoid slowing every build?Inside `@TaskAction`/`doLast` (execution time), not in the task's configuration block, so it only runs when the task actually executes.
Configuration is reading the whole recipe and laying out ingredients; Execution is actually cooking the dishes you ordered, in the order the recipe requires.
saying these in an interview costs you the question
- Saying task actions run during Configuration.
- Claiming code in a register{} block runs only when the task executes — it runs at configuration time.