skip to content

What is a lifecycle (aggregate) task with no actions, and how does it differ from a task that has doLast actions?

level: middleimportance: should knowfreq 45%

answer

  1. lifecycle = empty action list
  2. build / check / assemble
  3. groups via dependsOn
  4. worker = has actions
  5. keep aggregates action-free

basics

~20 s

A lifecycle task has an empty action list — it does no work itself and exists only to group/depend on other tasks (like build or check). A task with doLast actions actually performs work when executed.

solid answer

~40 s

Gradle tasks split into two informal roles. A **lifecycle (or aggregate) task** has an **empty action list**: no `@TaskAction`, no `doFirst`/`doLast`. Its purpose is to be a stable entry point that depends on other tasks — `build`, `check`, `assemble` are the canonical examples. Running it just triggers its `dependsOn` graph; the lifecycle task itself reports as executed but does nothing. A **worker task** has one or more actions (a `@TaskAction` and/or `doFirst`/`doLast`) and performs real work. Practically: you wire `dependsOn` on a lifecycle task to compose pipelines, and you put actual behavior in worker tasks. Adding a `doLast` to what was a lifecycle task turns it into a worker task — usually a smell, because lifecycle tasks should stay action-free so they remain cheap, predictable, and overridable.

code

kotlin · 10 lines
kotlin
// Lifecycle task: groups others, does no work itself
tasks.register("verify") {
    group = "verification"
    dependsOn("test", "detekt")
}

// Worker task: actually does something
tasks.register("stamp") {
    doLast { file("build/stamp.txt").writeText("ok") }
}

go deeper

for a junior

Recognize build/check as tasks that 'run other tasks' and don't do work themselves.

for a middle

Define lifecycle task as empty-action-list aggregate vs worker task with actions; create one with dependsOn.

for a senior

Argue for keeping aggregates action-free and modeling glue as dependsOn'd worker tasks for caching and clarity.

for a principal

Establish conventions for pipeline structure: aggregate tasks as stable contracts, real work in cacheable typed tasks.

## Two roles for tasks Gradle doesn't have a separate 'lifecycle task' type — the distinction is about whether the task has **actions**. ### Lifecycle / aggregate task - **Empty action list** (no `@TaskAction`, no `doFirst`/`doLast`). - Exists purely to **group** other tasks via `dependsOn`. - Examples from the base/Java plugins: `build`, `check`, `assemble`, `clean`'s siblings. - Running it executes its dependencies; the task itself does nothing and finishes instantly. ```kotlin tasks.register("qa") { group = "verification" description = "Runs all quality checks" dependsOn("test", "detekt", "ktlintCheck") // no doLast -> pure lifecycle task } ``` ### Worker task - Has at least one action. - Does real work when executed (and participates in up-to-date checks if it declares inputs/outputs). ## Why keep lifecycle tasks action-free - **Predictability**: `./gradlew build` should mean 'produce the standard outputs', delegating to real tasks. Hidden work in a lifecycle task surprises everyone. - **Up-to-date / caching**: an action-free task can't really be 'up-to-date' in a useful way; mixing a `doLast` into it muddies output reporting. - **Composability**: teams extend lifecycle tasks by adding `dependsOn`, not by stuffing logic inside. ## Relationship to the action list A lifecycle task is simply a task whose action list is empty. The moment you call `doLast { }` on it, you append an action and it stops being a pure lifecycle task. If you need glue logic, prefer a dedicated worker task that the lifecycle task `dependsOn`, rather than adding actions to the aggregate.

  • Name two built-in lifecycle tasks and what they aggregate.
    `check` aggregates verification tasks (test, lint); `build` aggregates `check` + `assemble` to produce and verify all outputs. Neither does work itself.
  • Is adding a doLast to the build task a good idea?
    Usually no. It hides work in an aggregate task. Prefer a dedicated worker task that build dependsOn, keeping the lifecycle task action-free and predictable.

saying these in an interview costs you the question

  • Claiming lifecycle tasks run their own logic.
  • Saying `build` has a @TaskAction doing the compilation.
  • Treating dependsOn as if it were a doFirst action.

context