skip to content

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%

answer

  1. both order, neither selects
  2. must = hard guarantee
  3. should = soft, droppable
  4. should dropped on cycle or for parallelism
  5. must serializes parallel tasks

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.

solid answer

~40 s

`mustRunAfter` and `shouldRunAfter` are **ordering rules**, not dependencies: neither pulls a task into the graph. They only matter when both tasks are already scheduled for some other reason. `taskB.mustRunAfter(taskA)` is a **hard** constraint — if both run, B is guaranteed to start after A finishes. `shouldRunAfter` is a **soft** constraint that Gradle honors *when reasonable* but will silently drop in two situations: (1) honoring it would create an ordering **cycle**, and (2) it would otherwise reduce parallelism unnecessarily. So `shouldRunAfter` is a 'nice to have' hint; `mustRunAfter` is a guarantee. Choose `mustRunAfter` when correctness/observability depends on the order (e.g. integration tests after unit tests so a unit failure surfaces first deterministically). Choose `shouldRunAfter` for a *preference* you're happy to sacrifice for speed or to break a would-be cycle.

code

kotlin · 6 lines
kotlin
tasks.named<Test>("integrationTest") {
    mustRunAfter(tasks.named("test"))   // hard order: unit before integration
}
tasks.named("lint") {
    shouldRunAfter("format")            // soft: prefer, but droppable for speed / cycles
}

go deeper

for a junior

Know both only order, not schedule; must is strict, should is a preference.

for a middle

Explain the two cases where shouldRunAfter is dropped (cycle, parallelism) and give the integration-after-unit example.

for a senior

Discuss serialization points under --parallel and why correctness must never depend on shouldRunAfter.

for a principal

Reason about build-throughput tradeoffs: where hard ordering creates parallel bottlenecks across a large graph and where soft hints preserve speed.

## Both are ordering-only Neither `mustRunAfter` nor `shouldRunAfter` adds an edge that *selects* a task. They constrain the *relative order* of tasks that are independently scheduled. If only one of the two tasks is requested, both rules are no-ops. ```kotlin tasks.named("integrationTest") { mustRunAfter("test") // hard: if both run, unit tests first } tasks.named("lint") { shouldRunAfter("format") // soft: prefer format first, but may be dropped } ``` ## The hard/soft difference - **`mustRunAfter`** — Gradle *always* honors it (when both tasks are in the graph). If a `mustRunAfter` plus the real dependency edges form a cycle, the build fails with a circular-dependency error. - **`shouldRunAfter`** — a *preference*. Gradle ignores it when: - honoring it would create a **cycle** (it's dropped instead of failing), or - it would needlessly serialize work that could run in parallel. Because `shouldRunAfter` can be dropped, you must never rely on it for correctness. ## Interaction with parallel execution With `--parallel` (or parallel task execution within a project), Gradle runs independent tasks concurrently. `mustRunAfter` forces a serialization point: B won't start until A completes, even if both could otherwise run together. `shouldRunAfter` expresses the same preference but *yields* to parallelism — if serializing would hurt throughput, Gradle may run them concurrently and ignore the hint. ## Typical pattern ```kotlin // Make 'check' run unit tests before slower integration tests, deterministically val integrationTest = tasks.register<Test>("integrationTest") integrationTest { mustRunAfter(tasks.named("test")) } tasks.named("check") { dependsOn(integrationTest) } // dependsOn pulls it in ``` Here `dependsOn` *selects* `integrationTest`; `mustRunAfter` *orders* it after the unit `test` task. The two roles are complementary and frequently combined. ## Mental model - Need the task to *run*? → `dependsOn` (or inferred provider wiring). - Need a *guaranteed order* among already-running tasks? → `mustRunAfter`. - *Prefer* an order but fine to sacrifice for speed / cycle-avoidance? → `shouldRunAfter`.

  • What happens if a `shouldRunAfter` would introduce a cycle?
    Gradle drops the soft ordering rather than failing — the build still runs, just without that preferred order.
  • If a `mustRunAfter` plus real dependencies form a cycle, what happens?
    The build fails with a circular dependency error; `mustRunAfter` is hard and cannot be silently dropped.
  • Does `mustRunAfter` pull the target task into the graph?
    No. Like all ordering hooks it only orders already-selected tasks; you still need dependsOn or a request to schedule them.

saying these in an interview costs you the question

  • Treating `shouldRunAfter` as a reliable ordering guarantee.
  • Believing either hook schedules/selects the target task.
  • Saying `mustRunAfter` creates a dependency that runs the other task.

context