How do dependsOn, finalizedBy, mustRunAfter, and shouldRunAfter each shape the task graph, and how do they differ?
answer
- dependsOn = pulls in + runs before
- finalizedBy = pulls in + runs after, even on failure
- mustRunAfter = order only, no pull-in
- shouldRunAfter = soft order, can be dropped
- two categories: membership vs ordering
basics
~20 sdependsOn adds a real edge that pulls the dependency into the graph. finalizedBy schedules a task to run after another, even on failure. mustRunAfter/shouldRunAfter only constrain ordering — they don't add the task to the graph.
solid answer
~40 sThese relationships wire the DAG differently. **`dependsOn`** creates a hard dependency edge: if task B `dependsOn` A, then requesting B *pulls A into the graph* and runs A first. **`finalizedBy`** attaches a finalizer: if A is `finalizedBy` C, then whenever A is scheduled, C is added and runs *after* A — even if A fails (great for cleanup/reports). **`mustRunAfter`** is a pure ordering constraint: it says 'if both X and Y are already in the graph, run X after Y', but it does **not** pull Y in. **`shouldRunAfter`** is the same but a soft hint — Gradle relaxes it to break ordering cycles or improve parallelism. So `dependsOn`/`finalizedBy` affect *which* tasks are in the graph; `mustRunAfter`/`shouldRunAfter` only affect *order* among tasks already present.
code
kotlin · 7 linesval startServer = tasks.register("startServer")
val stopServer = tasks.register("stopServer")
val integTest = tasks.register("integTest") {
dependsOn(startServer)
finalizedBy(stopServer) // runs even if integTest fails
}
tasks.register("report") { mustRunAfter(integTest) }go deeper
Define dependsOn and finalizedBy and that finalizers run even on failure.
Clearly separate membership (dependsOn/finalizedBy) from ordering-only (mustRunAfter/shouldRunAfter).
Discuss real scenarios (server lifecycle, report-after-test) and avoiding dependsOn misuse for pure ordering.
Reason about how ordering hints interact with parallel execution and how to design relationships that keep incremental graphs lean.
## Edges vs. ordering constraints The task graph has two kinds of relationships, and the key distinction is **'does it pull a task into the graph?'** ### dependsOn — hard dependency (pulls in) `b.dependsOn(a)` means: to run `b`, `a` must run first, and requesting `b` *adds* `a` to the graph transitively. ### finalizedBy — finalizer (pulls in, runs after, even on failure) `a.finalizedBy(c)` means: whenever `a` is scheduled to run, `c` is added and runs **after** `a`. Crucially `c` runs even if `a` **fails or is up-to-date**. Classic use: stopping a server or publishing a report after a test task. ### mustRunAfter — strict ordering (does NOT pull in) `x.mustRunAfter(y)` says: *if* both `x` and `y` are in the graph, `x` runs after `y`. If `y` isn't requested, nothing happens. It never adds `y`. ### shouldRunAfter — soft ordering (does NOT pull in) Same as `mustRunAfter` but Gradle may **ignore** it to resolve an ordering cycle or to parallelize. It's a preference, not a guarantee. ## Why two categories exist Mixing concerns is a common mistake: people use `dependsOn` to force ordering and accidentally drag heavy tasks into every build. If you only need ordering *when both already run*, use `mustRunAfter`. ```kotlin val startServer = tasks.register("startServer") val stopServer = tasks.register("stopServer") val integTest = tasks.register("integTest") { dependsOn(startServer) // pulls startServer in, runs it first finalizedBy(stopServer) // stopServer runs after, even if integTest fails } tasks.register("report") { mustRunAfter(integTest) } // only ordered if both run ``` ## Implicit dependencies Note that declaring one task's input as another task's output also creates an edge automatically (covered separately), so `dependsOn` is not the only way edges appear.
- A finalizer's main task fails. Does the finalizer still run?Yes. `finalizedBy` guarantees the finalizer runs after the finalized task whether it succeeds, fails, or is up-to-date — which is exactly why it suits cleanup like stopping a server.
- When would you choose mustRunAfter over dependsOn?When you want ordering only if both tasks happen to be in the same graph, without forcing the second task to run. E.g., 'if tests and lint both run, run lint after tests' — but `gradle lint` alone should not trigger tests.
- Why does shouldRunAfter exist if mustRunAfter already orders tasks?shouldRunAfter is relaxable: Gradle drops it to break an ordering cycle or to enable parallelism, so it expresses a preference without risking a hard failure.
saying these in an interview costs you the question
- Saying mustRunAfter adds the task to the graph (it does not).
- Claiming a finalizer is skipped when its task fails.
- Using dependsOn purely for ordering, accidentally dragging tasks into builds that shouldn't run them.