skip to content

Inside a build, how can you programmatically inspect the full set of tasks Gradle has scheduled, and what should you be careful about when doing so?

level: middleimportance: should knowfreq 30%

answer

  1. taskGraph.allTasks ordered List<Task>
  2. valid only after whenReady
  3. match on .path not .name
  4. getDependencies for predecessors
  5. config-cache: capture plain values

basics

~10 s

Use gradle.taskGraph.allTasks after the graph is ready (e.g. in whenReady) to get the ordered List<Task> of scheduled tasks. Match on task.path (absolute) for precision in multi-project builds.

solid answer

~40 s

`gradle.taskGraph.allTasks` returns the full, dependency-ordered list of scheduled tasks, but it's only valid after the graph is populated — query it inside `gradle.taskGraph.whenReady` or during execution, not during configuration. From there you can compute things like 'is `publish` scheduled?' via `allTasks.any { it.path == ":lib:publish" }`, or inspect ordering and per-task dependencies with `getDependencies(task)`. Cautions: (1) accessing `allTasks` forces the entire graph to be realized — fine in `whenReady`, but don't trigger it prematurely. (2) Use `.path` (absolute, e.g. `:lib:test`) rather than `.name` to disambiguate same-named tasks across subprojects. (3) Under the configuration cache, avoid capturing the live `Task`/`Gradle` references into task actions; read what you need inside the callback and store plain values.

code

kotlin · 9 lines
kotlin
gradle.taskGraph.whenReady {
    // ordered list of scheduled tasks
    allTasks.forEach { t ->
        val deps = getDependencies(t).map { it.path }
        logger.lifecycle("\${t.path} <- \${deps}")
    }
    val willPublish = allTasks.any { it.path == ":lib:publish" }
    logger.lifecycle("publish scheduled: \$willPublish")
}

go deeper

for a junior

Know that taskGraph.allTasks lists the scheduled tasks; mainly for logging/inspection.

for a middle

Use it inside whenReady, match on path, and know getDependencies; avoid premature realization.

for a senior

Reason about lazy configuration / configuration avoidance and configuration-cache-safe extraction of plain values.

for a principal

Standardize graph-introspection patterns across shared plugins so they stay configuration-cache compatible and don't defeat configuration avoidance at scale.

## The TaskExecutionGraph API `gradle.taskGraph` is a `TaskExecutionGraph`. Once Gradle has finished configuration and **populated** the graph, it exposes the scheduled plan: - `allTasks: List<Task>` — every scheduled task, in execution order. - `hasTask(path: String)` / `hasTask(task: Task)` — membership checks. - `getDependencies(task: Task): Set<Task>` — the direct graph predecessors of a task (its scheduled dependencies). - `whenReady { ... }` / `addTaskExecutionGraphListener(...)` — hooks fired when the graph is ready. ### Reading the scheduled set ```kotlin gradle.taskGraph.whenReady { val scheduledPaths = allTasks.map { it.path } logger.lifecycle("Scheduled: \n" + scheduledPaths.joinToString("\n")) val publishing = allTasks.any { it.path == ":lib:publish" } if (publishing) logger.lifecycle("Release build detected") } ``` ### Timing: graph must be populated `allTasks` (and `hasTask`) require the graph to exist. Inside the configuration phase, before population, the graph isn't ready — querying it is invalid. The correct, single place to branch on the whole plan is `whenReady`, which Gradle fires exactly once after configuration and before any task executes. ### path vs name In a multi-project build, two subprojects can each have a `test` task. `allTasks` will contain both, distinguished by `path` (`:app:test`, `:lib:test`) while sharing a `name` (`test`). Always match on `path` for correctness. ### Performance / realization caveat Gradle uses **lazy task configuration** (`tasks.register`, `Provider`-based wiring) so tasks aren't configured until needed. Touching `allTasks` realizes the scheduled tasks. That's expected inside `whenReady` (the plan is already fixed), but doing graph-wide introspection eagerly elsewhere can defeat configuration avoidance. ### Configuration cache friendliness The configuration cache forbids capturing certain live objects (the `Gradle` instance, arbitrary `Task` references) into task actions. The safe pattern is to read what you need *inside* the `whenReady` callback and store plain serializable values (strings, booleans), rather than holding `Task` objects to inspect later at execution time. ### Summary `allTasks` is your window into the executed plan; treat it as read-after-ready, key off `path`, and extract plain data for any execution-time use.

  • Why match on task.path rather than task.name?
    In multi-project builds different subprojects can share a task name (e.g. test). The path is absolute (:app:test vs :lib:test) and uniquely identifies the task.
  • What does getDependencies return?
    The set of direct scheduled predecessor tasks of the given task in the graph — useful for understanding why something runs before something else.
  • Any configuration-cache concern with allTasks?
    Yes — don't capture live Task or Gradle references into task actions. Read needed values (paths, booleans) inside whenReady and store plain data.

saying these in an interview costs you the question

  • Querying allTasks during configuration before the graph is ready.
  • Matching on .name and getting the wrong subproject's task.
  • Capturing live Task/Gradle objects into actions under the configuration cache.

context