skip to content

Execution Phase

Selecting the requested tasks, ordering them by the DAG, running their actions, and reporting outcomes and failures. Asked as the third leg of the lifecycle answer, usually followed by 'so what does UP-TO-DATE mean here?'

on this pageshow

questions

6

What happens during Gradle's Execution phase, and how does it differ from the Configuration phase?

level: juniorimportance: must knowfreq 70%

answer

  1. third / last phase
  2. runs @TaskAction + doFirst/doLast
  3. config builds objects, exec runs work
  4. outcome labels UP-TO-DATE / SKIPPED
  5. command-line tasks + their deps

basics

~20 s

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

A 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 lines
kotlin
tasks.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

for a junior

Name the three phases and state that Execution runs the actions of the requested tasks in dependency order.

for a middle

Distinguish configuration-time vs execution-time code, and list the outcome labels (UP-TO-DATE, SKIPPED, FROM-CACHE).

for a senior

Explain how task selection + graph ordering feeds Execution, and why misplacing code in configuration blocks hurts every build.

for a principal

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.

context

open as a page

How does Gradle decide which tasks to execute and in what order during the Execution phase?

level: middleimportance: must knowfreq 65%

basics

~20 s

Gradle starts from the tasks you named on the command line, pulls in every task they depend on (dependsOn), then topologically sorts the whole set. It runs them in an order that respects dependencies and mustRunAfter/shouldRunAfter ordering rules.

open as a page

How does Gradle decide a task is UP-TO-DATE during execution, and what makes it re-run?

level: middleimportance: must knowfreq 60%

basics

~20 s

Before running a task, Gradle snapshots its declared inputs and outputs. If both match the previous run (and outputs still exist), it skips the action and reports UP-TO-DATE. Any change to an input, output, or the task's code makes it re-run.

open as a page

What happens when a task fails during execution, and how does `--continue` change Gradle's behavior?

level: middleimportance: should knowfreq 45%

basics

~20 s

By default Gradle stops at the first task failure (fail-fast): no further tasks that depend on it run, and the build exits non-zero. With --continue, Gradle keeps running every task whose dependencies still succeeded, then reports all failures together at the end.

open as a page

Explain the different task outcomes Gradle reports during execution (UP-TO-DATE, SKIPPED, NO-SOURCE, FROM-CACHE) and what triggers each.

level: seniorimportance: should knowfreq 40%

basics

~20 s

No label = the action ran. UP-TO-DATE = inputs/outputs unchanged. FROM-CACHE = outputs pulled from the build cache. SKIPPED = an onlyIf {} predicate was false (or the task was excluded). NO-SOURCE = the task's declared source inputs were empty.

open as a page

Walk through what Gradle does internally when it executes a single task — from the task graph to the @TaskAction.

level: seniorimportance: should knowfreq 30%

basics

~20 s

For each task in graph order, Gradle resolves its inputs, runs up-to-date/cache checks, and if work is needed runs the task's action list — doFirst actions, then @TaskAction methods, then doLast actions — capturing outputs and recording an outcome.

open as a page