skip to content

Ad-hoc & DefaultTask Tasks

Untyped tasks assembled from doLast plus group, description, and dependsOn, and how they differ from a typed task class. Interviewers ask to hear why ad-hoc tasks are fine as glue but poor citizens for caching.

on this pageshow

questions

5

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

open as a page

What is an ad-hoc task in Gradle, and how do you define one?

level: juniorimportance: must knowfreq 70%

basics

~10 s

An ad-hoc task is an untyped task of type DefaultTask whose behavior you add inline with doLast { ... }. You register it with tasks.register("name") and put the action in the closure.

open as a page

An ad-hoc task prints a value every single build even when you run an unrelated task. What is going on and how do you fix it?

level: middleimportance: must knowfreq 50%

basics

~20 s

The print is outside doLast, so it runs in the configuration phase, which executes every build for all projects. Move the side-effecting code inside doLast { ... } so it only runs when the task executes.

open as a page

How do you set the group, description, and dependsOn of an ad-hoc task, and what does each one do?

level: middleimportance: should knowfreq 55%

basics

~10 s

Inside the register block set group (the category in gradle tasks), description (the help text), and call dependsOn(...) to declare tasks that must run before this one. All three are properties on every task.

open as a page

When would you choose a typed (or custom) task over an ad-hoc doLast task, and what do you lose by staying ad-hoc?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Use a typed/custom task when logic is reused, needs declared inputs/outputs for up-to-date checks and caching, or needs unit testing. Ad-hoc doLast tasks are fine for tiny one-off glue but get none of that.

open as a page