skip to content

Task Dependencies & Ordering Hooks

Wiring dependencies from inside a task type: provider-inferred edges, dependsOn, ordering rules, and finalizers. Interviewers ask because a well-authored task declares its own wiring instead of pushing that onto users.

on this pageshow

questions

5

What does `dependsOn` do in Gradle, and how is a task dependency different from controlling the mere ordering of two tasks?

level: juniorimportance: must knowfreq 80%

answer

  1. dependsOn = add + order
  2. ordering hooks = order only
  3. requesting A pulls in its dependsOn
  4. mustRunAfter no-op if other task absent
  5. DAG built then executed

basics

~20 s

dependsOn declares that one task requires another to run first; if you ask for task A, Gradle also runs its dependencies. A pure ordering hook only sequences tasks that are already scheduled — it never adds a task to the build.

solid answer

~40 s

`taskA.dependsOn(taskB)` says: whenever `taskA` is in the task graph, `taskB` must also be in the graph and must complete before `taskA`. Requesting `taskA` therefore *pulls in* `taskB`. This is a true dependency: it both **adds** the dependency to the execution graph and **orders** it. That is fundamentally different from the ordering hooks `mustRunAfter` / `shouldRunAfter`. Those only constrain *relative order* between tasks **that are already going to run** for some other reason; they never cause a task to be scheduled. So `test.mustRunAfter(compile)` does nothing if `compile` isn't already requested. Rule of thumb: reach for `dependsOn` only when A genuinely cannot produce a correct result without B's output; reach for ordering hooks when both run anyway but the sequence matters.

code

kotlin · 8 lines
kotlin
val compile = tasks.register("compileThing")
val pkg = tasks.register("packageThing") {
    dependsOn(compile) // pulls compileThing into the graph and orders it first
}
// vs ordering only:
tasks.named("smokeTest") {
    mustRunAfter("unitTest") // orders unitTest first ONLY if unitTest is already scheduled
}

go deeper

for a junior

Define dependsOn as 'run this other task first' and know it pulls the task in.

for a middle

Crisply contrast dependency edges vs pure ordering, and note ordering hooks are no-ops when the other task isn't scheduled.

for a senior

Explain when to prefer ordering over dependsOn to keep tasks independently runnable, and mention inferred dependencies as the preferred alternative.

for a principal

Frame as graph-coupling governance: over-using dependsOn bloats graphs and hurts incrementality/parallelism across a large multi-project build.

## The task graph When you run `./gradlew build`, Gradle first builds a **directed acyclic graph (DAG)** of tasks, then executes it. Two distinct things determine that graph: 1. **Which tasks are in it** (the *task selection* / dependency edges). 2. **In what order** the selected tasks run. `dependsOn` affects **both**. `mustRunAfter` / `shouldRunAfter` affect **only ordering** of tasks already selected. ## `dependsOn` ```kotlin val generate = tasks.register("generate") { /* ... */ } tasks.register("package") { dependsOn(generate) // package needs generate's work first } ``` Requesting `package` now guarantees `generate` runs and finishes first. `dependsOn` accepts tasks, task names (Strings), `TaskProvider`s, `Provider`s, `Buildable`s, or even a closure/`Callable` returning any of those, so dependencies can be computed lazily. ## Ordering hooks are NOT dependencies ```kotlin tasks.named("integrationTest") { mustRunAfter("test") // only orders them IF both are scheduled } ``` If you run only `integrationTest`, `test` does **not** run — `mustRunAfter` added no edge that pulls it in. This is the single most common confusion in interviews. ## Why the distinction matters - Over-using `dependsOn` couples tasks and bloats the graph (running `A` drags in `B` even when you didn't want `B`). - Pure ordering needs (e.g. "if both unit and integration tests run, do unit first") should use `mustRunAfter`, not `dependsOn`, so each remains independently runnable. ## Preferred modern style: inferred dependencies In current Gradle you usually should NOT write `dependsOn` by hand at all — wire a *producer task's output Provider* into a consumer's input property and Gradle infers the dependency automatically (covered in the related question). `dependsOn` remains the explicit, coarse-grained escape hatch.

  • If I run only `packageThing`, does `compileThing` run?
    Yes — `dependsOn` pulls it into the graph, so it is scheduled and runs first.
  • If I run only `smokeTest`, does `unitTest` run?
    No. `mustRunAfter` is pure ordering; it never schedules `unitTest`, so nothing pulls it in.

dependsOn is "you can't bake the cake until the oven is preheated — so go preheat it." mustRunAfter is "if you happen to be doing both chores, do the dishes before drying them" — but it never makes you do the dishes.

saying these in an interview costs you the question

  • Claiming `mustRunAfter` adds a task to the graph (it does not).
  • Saying `dependsOn` only orders tasks — it also selects/pulls them in.

context

open as a page

What is an inferred (implicit) task dependency, and how does wiring a producer's output Provider into a consumer's input property make explicit `dependsOn` unnecessary?

level: middleimportance: must knowfreq 70%

basics

~20 s

If you connect a producing task's output Provider (e.g. its RegularFileProperty) directly to a consuming task's input property, Gradle sees the output 'carries' its producing task and automatically adds the dependency — no manual dependsOn needed.

open as a page

What is `finalizedBy`, what are its key semantics (especially on failure), and what is a canonical use case?

level: middleimportance: should knowfreq 50%

basics

~20 s

a.finalizedBy(b) schedules b to run after a, even if a fails. It's used for guaranteed cleanup or teardown — like stopping a server or collecting reports — that must happen whether or not the main task succeeded.

open as a page

Compare `mustRunAfter` and `shouldRunAfter`. When would you choose one over the other, and how do they interact with parallel execution?

level: middleimportance: should knowfreq 55%

basics

~20 s

Both only order tasks that are already scheduled, never adding tasks. mustRunAfter is a hard ordering constraint Gradle always honors. shouldRunAfter is a soft preference Gradle can break to avoid a cycle or to enable more parallelism.

open as a page

How do you wire dynamic, computed task dependencies inside a custom task type — for example via `TaskDependency` / `Buildable` or a `@TaskAction`-time set, and what is `getTaskDependencies()` for?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Task dependencies are computed at graph-construction time, not execution time. To make them dynamic you pass a Callable/closure or Provider to dependsOn, or implement Buildable.getBuildDependencies() on an input value so the value 'carries' the tasks that build it.

open as a page