In a Gradle task definition, what is the difference between code in the configuration block and code inside doFirst{}/doLast{}, and when does each run?
answer
- config phase vs execution phase
- register body = configuration
- doLast closure = execution-only
- actions skip when UP-TO-DATE
- work in actions, wiring in config
basics
~10 sConfiguration-block code runs during the configuration phase for every build invocation. doFirst{}/doLast{} are action closures that run only in the execution phase, and only if the task actually executes.
solid answer
~40 sGradle runs in phases: **initialization**, **configuration**, then **execution**. Code written directly in a `tasks.register("x") { ... }` block (the configuration block) runs during the *configuration* phase — every build, regardless of whether the task ends up in the task graph or runs. Code inside `doFirst { }` and `doLast { }` is added to the task's *action list* and runs during the *execution* phase, only if the task is actually selected and not up-to-date/skipped. So setting properties, wiring inputs/outputs, and dependencies belongs in the configuration block; the actual work (file copying, running a tool, printing results) belongs in `doLast`. Putting work in the configuration block makes it run on *every* invocation — a classic performance bug and the core idea behind configuration avoidance.
code
kotlin · 9 linestasks.register("report") {
group = "verification" // configuration phase (every build)
val outFile = layout.buildDirectory.file("report.txt")
outputs.file(outFile)
doLast { // execution phase (only if run)
outFile.get().asFile.writeText("done")
println("wrote report")
}
}go deeper
Know the basic split: register-block code configures (always runs), doLast does the actual work at execution time.
Articulate the three phases and that actions are skipped for up-to-date/skipped tasks; tie to where to place side effects.
Connect to configuration avoidance and configuration-time performance; explain why side effects in config break incremental builds and config cache.
Frame as a team convention: enforce 'no side effects at configuration time' to keep configuration cache compatible and builds fast at scale.
## The three build phases Every Gradle build runs in three ordered phases: 1. **Initialization** — Gradle decides which projects participate (settings.gradle). 2. **Configuration** — Gradle evaluates build scripts and *configures* every task object it needs to know about. The configuration block of a task runs here. 3. **Execution** — Gradle works out the requested task graph and runs the **actions** of each selected task, in order. ## Configuration block vs action block When you write: ```kotlin tasks.register("greet") { // <-- configuration block group = "demo" description = "says hi" doLast { // <-- action block (execution phase) println("hello") } } ``` The body of the `register` lambda (`group =`, `description =`, the *call* to `doLast`) executes during the **configuration phase**. The closure *passed to* `doLast` is stored in the task's action list and only invoked during the **execution phase**, and only if `greet` is actually run. ## Why it matters - Anything expensive or with side effects (reading files, calling external services, `println` of results) put **inside an action** (`doLast`), never in the configuration block. Configuration-block code runs on *every* build — even `./gradlew help` — which slows down every invocation. - `doFirst`/`doLast` only fire when the task executes. If the task is `UP-TO-DATE`, `SKIPPED`, or `NO-SOURCE`, its actions do **not** run. - This distinction is the foundation of **configuration avoidance**: `tasks.register` (lazy) configures a task only when it's needed, versus `tasks.create` (eager) which configures immediately. ## The action list A task is really an ordered list of `Action<Task>` objects. `doFirst` prepends, `doLast` appends. A task with no actions (a lifecycle task like `build`) simply has an empty action list and does nothing itself.
- Why is putting println("building") directly in the register block usually a mistake?It runs during the configuration phase on every single invocation (even unrelated tasks like `help`), not when the task executes. Side effects belong in a doLast action.
- If a task is UP-TO-DATE, do its doLast actions run?No. When a task is up-to-date (or skipped/no-source), its action list is not executed at all.
The configuration block is writing the recipe card (always done when you open the cookbook); doFirst/doLast are actually cooking — only happens when you decide to make that dish.
saying these in an interview costs you the question
- Saying doLast runs during the configuration phase.
- Claiming the whole register{} body runs only when the task executes.
- Putting expensive work or println of results in the configuration block.