skip to content

What is gradle.taskGraph.whenReady{} and when does the closure it registers actually run?

level: juniorimportance: must knowfreq 55%

answer

  1. end of configuration / start of execution
  2. fires exactly once
  3. receives TaskExecutionGraph
  4. graph fully built, no action run yet
  5. global graph-aware decisions

basics

~10 s

whenReady registers a callback that runs once Gradle has finished building the task execution graph for the build, after configuration but before any task executes.

solid answer

~40 s

`gradle.taskGraph.whenReady { graph -> ... }` registers a closure on the `TaskExecutionGraph`. Gradle runs builds in three phases: **initialization**, **configuration**, and **execution**. The task graph (which tasks will run, in what order, based on the requested tasks plus their dependencies) is computed at the very end of configuration. `whenReady` fires exactly once at that boundary — after the whole graph is known but before the first task action runs. Inside it you receive the `TaskExecutionGraph`, so you can call `graph.allTasks` or `graph.hasTask(...)` to inspect what the build is about to do and react globally (e.g. set a property, fail fast, or print a summary). It is the canonical hook for 'I need to know the full set of tasks before execution starts'.

code

kotlin · 6 lines
kotlin
gradle.taskGraph.whenReady { graph ->
    println("Build will run ${graph.allTasks.size} tasks")
    if (graph.hasTask(":app:publish")) {
        println("Publish detected — enabling release mode")
    }
}

go deeper

for a junior

Know that it's a callback firing once after the graph is built, before tasks run, and that you can inspect which tasks will execute.

for a middle

Place it precisely at the configuration→execution boundary and name allTasks/hasTask as the inspection APIs.

for a senior

Contrast it with afterEvaluate and buildFinished, and explain why it's the earliest point the requested-task set is known.

for a principal

Discuss configuration-cache implications and why graph-ready hooks are a controlled, single global extension point rather than per-project lifecycle wiring.

## Build phases recap A Gradle build has three phases: 1. **Initialization** — Gradle decides which projects take part and creates `Project` instances. 2. **Configuration** — every build script runs top to bottom; tasks are *registered/created* and wired with dependencies. No task **action** runs here. 3. **Execution** — Gradle selects the requested tasks, builds the directed acyclic **task execution graph**, then runs each task's actions in dependency order. ## Where whenReady sits The task execution graph is finalized at the **end of configuration / start of execution** — the transition between phase 2 and phase 3. `gradle.taskGraph.whenReady { graph -> ... }` registers a callback that Gradle invokes **once**, precisely at that moment: the complete graph exists but no task action has run yet. `gradle` here is the `Gradle` object (the same one available as `project.gradle`); `taskGraph` is its `TaskExecutionGraph`. ## What you get inside The closure receives the `TaskExecutionGraph`. Two key read APIs: - `graph.allTasks` — an ordered `List<Task>` of every task that will execute, in execution order. - `graph.hasTask(":app:publish")` (or a `Task` instance) — whether a given task is in the graph. This makes `whenReady` the place for **global, graph-aware decisions**: fail the build early if an incompatible pair of tasks was requested, flip a flag when a `publish` task is present, print a plan, or set a system property only for certain runs. ## Two equivalent registration styles ```kotlin gradle.taskGraph.whenReady { if (it.hasTask(":app:release")) { version = version.toString().removeSuffix("-SNAPSHOT") } } ``` and the lower-level listener form: ```kotlin gradle.taskGraph.addTaskExecutionGraphListener { graph -> println("About to run ${graph.allTasks.size} tasks") } ``` Both fire at the same graph-ready moment; `whenReady` is the convenient closure sugar, `addTaskExecutionGraphListener` is the explicit listener interface (`TaskExecutionGraphListener.graphPopulated`). ## Why not just configuration time? During configuration you cannot yet know which tasks were *requested* or what the final graph looks like, because dependencies and the command line are still being resolved. `whenReady` is the earliest point where that answer exists.

  • Can task actions run inside the whenReady closure's view of the build?
    No. whenReady fires before the first task action executes — the graph is complete but execution hasn't started, which is exactly what makes it safe for fail-fast or global flag decisions.
  • How many times does whenReady fire per build?
    Exactly once, when the single task execution graph for the whole build is populated.

It's the moment the flight plan is filed and approved (graph built) but the plane hasn't pushed back yet (no task executed) — your last chance to inspect the whole route and abort or adjust before takeoff.

saying these in an interview costs you the question

  • Saying whenReady runs during configuration for each project — it runs once, after configuration, on the global graph.
  • Claiming it runs after execution finishes (that would be buildFinished, a different hook).

context