skip to content

What is the difference between doFirst and doLast on a task, and in what order do multiple actions run?

level: juniorimportance: must knowfreq 60%

answer

  1. doFirst prepends, doLast appends
  2. ordered action list
  3. doFirst repeated = reversed
  4. execution phase only
  5. plugins inject around existing actions

basics

~10 s

doLast adds an action to the end of the task's action list; doFirst adds one to the front. Both run in the execution phase. If you call doFirst twice, the later one runs first.

solid answer

~40 s

A task holds an ordered **list of actions**. `doLast { ... }` appends an action to the **end** of that list; `doFirst { ... }` prepends one to the **front**. At execution time, Gradle runs the list top to bottom. The subtle part is repeated calls: each `doFirst` is inserted at the very front, so the **last** `doFirst` you register runs **first**; each `doLast` is appended, so they run in registration order. So for a task with `doFirst A`, `doFirst B`, `doLast C`, `doLast D`, the run order is B, A, C, D. `doFirst` is typically used to set up or validate preconditions right before the task's main work; `doLast` for the main work or cleanup/reporting after. Both execute only in the execution phase, only when the task actually runs.

code

kotlin · 7 lines
kotlin
tasks.register("order") {
    doFirst { println("A") }
    doFirst { println("B") }   // inserted before A
    doLast  { println("C") }
    doLast  { println("D") }
}
// runtime order: B, A, C, D

go deeper

for a junior

Know doLast = end, doFirst = front, both in execution phase.

for a middle

Predict the run order with mixed/repeated doFirst/doLast and explain plugin injection use cases.

for a senior

Discuss using doFirst/doLast to augment built-in tasks vs. overriding behavior, and the phase implications for config-cache friendliness.

for a principal

Caution that piling doFirst/doLast onto shared tasks across plugins creates ordering fragility; prefer typed tasks or explicit task dependencies.

## The action list Every task carries an ordered list of `Action<Task>` objects. When Gradle executes the task, it invokes each action in list order. Typed tasks (like `Copy`) contribute a built-in action; ad-hoc tasks rely entirely on the `doFirst`/`doLast` actions you add. ## doFirst prepends, doLast appends - `doLast { ... }` → **adds to the end** of the list. - `doFirst { ... }` → **inserts at the beginning** of the list. Because `doFirst` always inserts at index 0, registering it multiple times reverses their relative order at runtime. ```kotlin tasks.register("order") { doFirst { println("A") } doFirst { println("B") } doLast { println("C") } doLast { println("D") } } // Execution order: B, A, C, D ``` `doFirst`s: last-registered-first (B then A). `doLast`s: registration order (C then D). ## When does this matter? - Plugins add behavior to existing tasks. A plugin can `doFirst` to inject a precondition before the original action, or `doLast` to add post-processing, without modifying the task's type. - `doFirst` is good for last-moment setup/validation; `doLast` for the work itself or reporting. ## Phase reminder Both `doFirst` and `doLast` bodies run in the **execution phase**, only when the task runs. Logic outside them runs at configuration time on every build. Putting actual work in `doFirst`/`doLast` keeps the configuration phase fast — important for build performance and for the configuration cache.

  • If a plugin wants to validate something right before a built-in task acts, which method does it use?
    doFirst — it prepends a validation action ahead of the task's main action.
  • Why might two doFirst calls surprise you?
    Because each inserts at the front, the most recently registered doFirst runs first, reversing their apparent order.

saying these in an interview costs you the question

  • Saying doFirst actions always run in registration order — repeated doFirst reverses them
  • Believing doFirst/doLast run at configuration time

context