skip to content

How do doFirst{} and doLast{} compose into a task's action list, and what is the execution order if you call them multiple times?

level: middleimportance: must knowfreq 65%

answer

  1. ordered actions list
  2. doLast = append (in order)
  3. doFirst = prepend (reverse order)
  4. @TaskAction sits in the middle
  5. hooks around tasks you don't own

basics

~20 s

A task holds an ordered list of actions. doLast appends to the end; doFirst prepends to the front. Multiple doFirst calls stack so the last-added doFirst runs first; doLast actions run in the order added.

solid answer

~40 s

Each task carries an ordered `actions` list. `doLast { }` **appends** an action to the end of the list; `doFirst { }` **prepends** one to the front. At execution time Gradle runs the list top to bottom. So `doLast` actions execute in the order you declared them. `doFirst` actions are trickier: because each prepends, the **last** `doFirst` you add ends up **first** in the list and therefore runs first — they execute in reverse declaration order. Any actions defined by the task type itself (e.g. a `@TaskAction` method, or `Copy`'s built-in copy action) sit in the middle: after all `doFirst` actions and before all `doLast` actions. This lets you inject setup before the task's real work and teardown/reporting after, without subclassing.

code

kotlin · 7 lines
kotlin
tasks.register("order") {
    doLast  { println("L1") }
    doLast  { println("L2") }
    doFirst { println("F1") }
    doFirst { println("F2") }
}
// gradlew order  ->  F2  F1  L1  L2

go deeper

for a junior

Know doLast appends and doFirst prepends; the simple two-block case.

for a middle

Correctly order multiple doFirst/doLast and place the @TaskAction in the middle; explain wrapping a task you don't own.

for a senior

Discuss using these hooks judiciously vs proper custom task types, and execution-time vs configuration-time concerns.

for a principal

Set policy: prefer typed tasks with declared inputs/outputs over ad-hoc doLast hooks that hurt cacheability and readability at scale.

## The action list model Internally a Gradle `Task` is essentially an ordered `List<Action<? super Task>>`. When the task executes, Gradle invokes each action in list order, passing the task itself as the argument. Three ways actions get into that list: - The task **type's own action** — for a custom task, a method annotated `@TaskAction`; for built-ins like `Copy`, an internal action. This is the task's primary work. - `doFirst { ... }` — **prepends** to the front of the list. - `doLast { ... }` — **appends** to the end of the list. ## Ordering rules Given: ```kotlin tasks.register("demo") { doLast { println("L1") } doLast { println("L2") } doFirst { println("F1") } doFirst { println("F2") } } ``` Resulting list (front to back): `F2, F1, <type action, none here>, L1, L2`. Output: `F2 F1 L1 L2`. - **doLast** actions run in the order declared (append keeps insertion order). - **doFirst** actions run in *reverse* declaration order (each new one jumps to the front). - The task's own `@TaskAction` always runs **after all doFirst** and **before all doLast**. ## Why prepend/append both exist This gives you hooks around a task you don't own. For a third-party or built-in task, you can't easily edit its `@TaskAction`, but you can `doFirst` to set something up (e.g. mutate the environment, log inputs) and `doLast` to assert/clean up afterwards. ## Practical notes - `doFirst`/`doLast` also accept a named overload (`doFirst("name") { }`) and an `Action`/`Closure`. - Inside the closure, `this` (or the `it` parameter in Kotlin) is the `Task`, so you can read its inputs/outputs at execution time. - Throwing inside a `doFirst` aborts the task before its main work and `doLast` actions; the task fails.

  • Two doFirst blocks — which runs first and why?
    The one declared LAST runs first. Each doFirst prepends to the front of the action list, so later additions jump ahead of earlier ones.
  • Where does a custom task's @TaskAction run relative to doFirst/doLast?
    After all doFirst actions and before all doLast actions — it sits in the middle of the action list.
  • Can you attach doFirst/doLast to a built-in task like jar or compileJava?
    Yes. You can configure any existing task and append/prepend actions to wrap its built-in behavior without subclassing it.

saying these in an interview costs you the question

  • Saying multiple doFirst blocks run in declaration order (they run reversed).
  • Claiming doLast actions run in reverse order.
  • Thinking doFirst replaces the task's main action rather than running before it.

context