How does Gradle decide which tasks to execute and in what order during the Execution phase?
answer
- requested tasks + transitive deps
- topological sort of DAG
- dependsOn = include + order
- mustRunAfter/shouldRunAfter = order only
- shouldRunAfter is droppable hint
basics
~20 sGradle starts from the tasks you named on the command line, pulls in every task they depend on (dependsOn), then topologically sorts the whole set. It runs them in an order that respects dependencies and mustRunAfter/shouldRunAfter ordering rules.
solid answer
~50 sAt the start of Execution, Gradle has a set of **requested** tasks (from the command line, or the default tasks). It expands that set by transitively following `dependsOn` and `finalizedBy` relationships to build the full **task execution graph** (a DAG). It then performs a topological sort so that every task runs after its dependencies. Pure dependencies (`dependsOn`) both *include* a task in the graph and *order* it. **Ordering-only** rules — `mustRunAfter` and `shouldRunAfter` — affect relative order but do **not** pull a task into the graph; they matter only when both tasks are already going to run. `mustRunAfter` is a hard ordering constraint; `shouldRunAfter` is a soft hint Gradle may drop to avoid a cycle or improve parallelism. With `--parallel` (or the configuration cache's parallel execution), independent tasks can run concurrently while still honoring all ordering edges.
code
kotlin · 9 linestasks.register("integTest") {
dependsOn("compileTestJava") // pulled in AND ordered first
mustRunAfter("unitTest") // ordered after unitTest ONLY if unitTest also runs
shouldRunAfter("checkstyle") // soft hint, may be ignored to avoid cycles
}
tasks.register("build") {
mustRunAfter("clean") // deterministic clean->build order without a hard dependency
}go deeper
Say Gradle runs the requested tasks plus their dependencies, in dependency order.
Clearly separate include+order (dependsOn) from order-only (mustRunAfter/shouldRunAfter) and explain the clean/build example.
Discuss the DAG, topological sort, cycle detection, finalizedBy, and how parallel execution honors ordering edges.
Reason about graph stability across a large multi-project build and how ordering rules interact with parallelism and the configuration cache.
## From request to graph Execution begins once Configuration is complete. Gradle knows the **requested** tasks: - those named on the command line (`gradle build test`), or - the project's `defaultTasks` if none were named. It then computes the **task execution graph** (`gradle.taskGraph`, a `TaskExecutionGraph`) by transitively resolving relationships: | Relationship | Pulls task into graph? | Affects order? | |---|---|---| | `dependsOn` | yes | yes (dependency runs first) | | `finalizedBy` | yes (finalizer added) | yes (finalizer runs after) | | `mustRunAfter` | **no** | yes, hard, only if both present | | `shouldRunAfter` | **no** | yes, soft hint, droppable | ## Topological ordering The graph is a DAG; Gradle topologically sorts it so each task runs only after its dependencies finish. A **cycle** in `dependsOn` is a configuration error ("Circular dependency between tasks"). ```kotlin tasks.register("compile") tasks.register("test") { dependsOn("compile") } // compile included AND ordered first tasks.register("lint") tasks.named("test") { mustRunAfter("lint") } // lint NOT pulled in; ordered before test only if lint also runs ``` Running `gradle test` runs `compile` then `test` — `lint` is *not* executed, because `mustRunAfter` does not add it. Running `gradle lint test` runs `lint` before `test`. ## Ordering vs dependency — the key distinction - **Dependency (`dependsOn`)**: "B needs A's *result*." Including + ordering. - **Ordering (`mustRunAfter`)**: "*If* both run, B must come after A" — e.g. run `clean` before `build` deterministically without making `build` depend on `clean`. ## Parallelism With `org.gradle.parallel=true` Gradle runs tasks from independent project subgraphs concurrently. Ordering edges are always respected; `shouldRunAfter` may be sacrificed to keep workers busy. You can hook `gradle.taskGraph.whenReady { }` to inspect the resolved graph before execution starts.
- Why prefer `mustRunAfter("clean")` over `dependsOn("clean")` for build?`dependsOn` would force a clean on every build, deleting outputs and killing incremental/up-to-date checking. `mustRunAfter` only orders them when both are explicitly requested.
- What error do you get from `a dependsOn b` and `b dependsOn a`?A configuration-time 'Circular dependency between tasks' error — the DAG cannot be topologically sorted.
- Does `mustRunAfter` guarantee the other task runs?No. It is ordering-only; it never adds the referenced task to the execution graph.
saying these in an interview costs you the question
- Claiming `mustRunAfter` causes the referenced task to be executed.
- Saying `dependsOn` only orders without including the dependency.
- Treating `shouldRunAfter` as a hard guarantee.