skip to content

Ordering: mustRunAfter & shouldRunAfter

mustRunAfter as a hard ordering constraint when both tasks run, and shouldRunAfter as a best-effort hint dropped on cycles — neither of which causes execution. A precise-distinction question that catches anyone who assumes ordering implies dependency.

on this pageshow

questions

5

What is the difference between an ordering rule (mustRunAfter/shouldRunAfter) and a task dependency in Gradle?

level: juniorimportance: must knowfreq 70%

answer

  1. dependency = pull in + run first
  2. ordering = sequence only IF both scheduled
  3. ordering never adds to graph
  4. run integTest alone vs with test
  5. decoupling sequencing from requirement

basics

~20 s

An ordering rule only fixes the relative order of two tasks IF both already end up in the task graph. It never adds a task to the graph or forces it to run, unlike a dependency, which both pulls a task in and runs it first.

solid answer

~40 s

A dependency (`dependsOn`) does two things: it adds the dependency task to the execution graph and guarantees it runs before the dependent task. An **ordering rule** (`mustRunAfter` / `shouldRunAfter`) does only one thing: it constrains relative order *if and only if* both tasks are already scheduled to run in the same build. If the other task is not in the graph (not requested, up-to-date-skipped, or excluded with `-x`), the ordering rule is simply ignored — nothing is added and nothing fails. The classic use case: you want `integTest` to run after `test` *when a user asks for both*, but you still want to be able to run `integTest` alone. `dependsOn` would force `test` to run every time; `mustRunAfter` gives you ordering without coupling. Ordering rules express *sequencing*, dependencies express *requirement*.

code

kotlin · 7 lines
kotlin
// Ordering without coupling: integTest runs after test ONLY when both are requested
tasks.named("integTest") {
    mustRunAfter(tasks.named("test"))
}

// vs. a dependency, which would force test to run every time integTest runs:
// tasks.named("integTest") { dependsOn("test") }

go deeper

for a junior

Be able to state that ordering controls sequence only, while a dependency pulls a task in and forces it to run.

for a middle

Give the integTest-after-test example and explain the 'run alone vs run together' behavior.

for a senior

Discuss why decoupling sequencing from requirement keeps builds flexible and avoids over-pulling tasks; mention interaction with -x and up-to-date.

for a principal

Frame ordering rules as a tool for shaping shared task-graph conventions across many modules without forcing unnecessary work, and the maintainability tradeoffs.

## The two distinct concepts Gradle's task graph (a directed acyclic graph) is built from two independent kinds of edges: 1. **Dependency edges** — created by `dependsOn`, by consuming another task's output (inferred), or by `finalizedBy`. A dependency edge says *"this task requires that task to be present and to have executed first."* It pulls the task **into** the graph. 2. **Ordering edges** — created by `mustRunAfter` and `shouldRunAfter`. An ordering edge says *"IF both of these tasks are already going to run, run them in this order."* It does **not** pull anything into the graph and does **not** force execution. ## Why the distinction matters Consider: ```kotlin tasks.named("integTest") { mustRunAfter("test") } ``` - Run `gradle test integTest` → both are scheduled, so `test` runs first, then `integTest`. - Run `gradle integTest` alone → `test` is **not** in the graph, so the rule is inert; `integTest` runs by itself. If you had instead written `dependsOn("test")`, running `integTest` alone would *always* drag `test` along — usually not what you want for a sequencing concern. ## Key properties of ordering rules - They are **conditional**: only effective when both tasks are in the same build's graph. - They are **non-additive**: they never add a task to the graph. - They interact with `--continue`, exclusions (`-x`), and up-to-date checks: if the predecessor is excluded or filtered out, ordering simply doesn't apply. ## Summary table | Feature | `dependsOn` | `mustRunAfter` / `shouldRunAfter` | |---|---|---| | Adds task to graph? | Yes | No | | Forces execution? | Yes | No | | Controls order? | Yes | Yes (if both scheduled) | Think of dependencies as *"I need this done"* and ordering rules as *"if we're both doing it, you go first."*

  • If you declare `integTest.mustRunAfter(test)` and run only `gradle integTest`, does `test` execute?
    No. `mustRunAfter` only orders tasks that are already in the graph. Since `test` was not requested, it is not added and does not run; `integTest` runs alone.
  • Can an ordering rule alone guarantee a task is built before another in a CI pipeline?
    No — because it doesn't pull the task into the graph. You need a dependency (or to request both tasks) to guarantee presence; ordering only sequences them once both are present.

A dependency is "you must eat dinner before dessert" (dinner is mandatory). An ordering rule is "if you happen to have both dinner and dessert tonight, eat dinner first" — but skipping dinner entirely is fine.

saying these in an interview costs you the question

  • Claiming mustRunAfter forces the predecessor to run (it does not).
  • Saying ordering rules and dependencies are interchangeable.

context

open as a page

Explain the difference between mustRunAfter and shouldRunAfter, including how each behaves when honoring the rule would create a cycle.

level: middleimportance: must knowfreq 60%

basics

~20 s

mustRunAfter is a hard ordering constraint that Gradle always honors when both tasks run. shouldRunAfter is best-effort: Gradle tries to honor it but silently drops it if honoring it would create an ordering cycle (or conflict with a real dependency).

open as a page

A teammate adds `release.mustRunAfter(build)` expecting that running `gradle release` will now also build. Why doesn't it, and what should they do instead?

level: middleimportance: should knowfreq 45%

basics

~10 s

mustRunAfter only orders tasks that are already scheduled; it never adds build to the graph. Running gradle release alone won't build. They need a real dependency (release.dependsOn(build)) or to request both tasks.

open as a page

What are the practical pitfalls of declaring ordering rules — eager vs lazy task references, accepting TaskProvider, and configuration-time cost — when wiring mustRunAfter/shouldRunAfter at scale?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Prefer passing TaskProviders or task names to mustRunAfter/shouldRunAfter and configure inside tasks.named { }, so you don't eagerly realize tasks at configuration time. Eagerly resolving with tasks.getByName(...) forces unnecessary task creation and hurts configuration performance.

open as a page

How do mustRunAfter and shouldRunAfter interact with parallel execution and determinism in a large Gradle build, and how would you use them to govern ordering across many subprojects?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Ordering rules only constrain relative order between the two named tasks; tasks with no ordering or dependency edge between them may run in parallel and in non-deterministic order. Use mustRunAfter/shouldRunAfter to force a specific sequence where it matters without making one task depend on the other.

open as a page