What is a lifecycle (aggregate) task with no actions, and how does it differ from a task that has doLast actions?
answer
- lifecycle = empty action list
- build / check / assemble
- groups via dependsOn
- worker = has actions
- keep aggregates action-free
basics
~20 sA 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 sGradle 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// 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
Recognize build/check as tasks that 'run other tasks' and don't do work themselves.
Define lifecycle task as empty-action-list aggregate vs worker task with actions; create one with dependsOn.
Argue for keeping aggregates action-free and modeling glue as dependsOn'd worker tasks for caching and clarity.
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.