skip to content

How does whenReady differ from afterEvaluate, and why might you choose one over the other?

level: seniorimportance: should knowfreq 38%

answer

  1. afterEvaluate = per project, end of config, no graph
  2. whenReady = whole build, graph built
  3. afterEvaluate for deferred property finalization
  4. whenReady for 'which tasks will run'
  5. prefer lazy Provider/Property where possible

basics

~10 s

afterEvaluate runs at the end of a project's configuration (per project, graph not yet built). whenReady runs once after the whole task graph is built. Use whenReady when you must know the requested/executed tasks.

solid answer

~40 s

`project.afterEvaluate {}` fires at the end of **that project's** configuration phase — useful when you need a value that other configuration code sets, but the **task execution graph does not exist yet**, so you cannot know which tasks were requested or will run. `gradle.taskGraph.whenReady {}` fires **once for the whole build**, after configuration of all projects completes and the **graph is fully built**, so `allTasks`/`hasTask` are available. Choose `afterEvaluate` for *deferred configuration* (read/finalize properties once the script has fully evaluated). Choose `whenReady` for *graph-aware* decisions: 'only do X if a publish/release task is in this run.' A common mistake is using `afterEvaluate` to ask 'is the user running release?' — that information isn't available until `whenReady`. Modern Gradle nudges you toward lazy `Provider`/`Property` wiring instead of both, but the lifecycle distinction still matters.

code

kotlin · 10 lines
kotlin
// Wrong: graph not known yet
project.afterEvaluate {
    // gradle.startParameter.taskNames is raw input, not the resolved plan
}

// Right: graph-aware decision
gradle.taskGraph.whenReady { graph ->
    val isRelease = graph.hasTask(":app:release")
    extra["release"] = isRelease
}

go deeper

for a junior

Know afterEvaluate is per-project end-of-config and whenReady is whole-build graph-ready.

for a middle

Match each hook to its use: deferred property vs graph-aware decision.

for a senior

Explain the graph-availability difference and why startParameter.taskNames isn't the execution plan.

for a principal

Argue for lazy Provider/Property over lifecycle hooks and reserve whenReady for genuine graph properties, considering configuration-cache constraints.

## Two different lifecycle moments | Hook | Scope | Timing | Graph available? | |------|-------|--------|------------------| | `project.afterEvaluate {}` | one project | end of **that project's** configuration | No | | `gradle.taskGraph.whenReady {}` | whole build | after **all** configuration, graph built | Yes | ### afterEvaluate Runs when a single project finishes evaluating its build script. Its job is **deferred configuration** — running logic after the rest of the script (or another plugin) has had a chance to set values: ```kotlin project.afterEvaluate { // someExtension.value is now final tasks.named<Jar>("jar") { archiveBaseName.set(myExtension.name) } } ``` At this point the task **graph has not been computed**, the command-line task selection isn't resolved into a plan, and you cannot reliably answer 'which tasks will run'. ### whenReady Runs once, after **every** project is configured and Gradle has built the single `TaskExecutionGraph`: ```kotlin gradle.taskGraph.whenReady { graph -> if (graph.hasTask(":app:release")) enableReleaseSigning() } ``` Now `allTasks`/`hasTask` reflect the real plan. ## Choosing - Need a **property finalized** by other config code? → `afterEvaluate` (or, better, lazy `Provider` wiring). - Need to **branch on which tasks are in the run**? → `whenReady`. ## The modern caveat Both are *imperative lifecycle hooks*. Gradle's lazy configuration (`Property<T>`, `Provider<T>`, `tasks.register`, `map`/`flatMap`) lets you express most 'compute later' logic **without** either hook, which keeps you configuration-cache compatible and avoids ordering surprises. Reach for `afterEvaluate`/`whenReady` only when a genuine lifecycle event — not just a deferred value — is what you need. 'Is the user running a release task?' is a legitimate `whenReady` case because it's truly a graph property. ## Common pitfall Nesting `afterEvaluate` inside another `afterEvaluate`, or trying to read the requested tasks in `afterEvaluate`, leads to brittle builds. If the question is about the **execution plan**, `whenReady` is the right and earliest answer.

  • Can afterEvaluate tell you which tasks the user requested on the command line?
    Not the resolved execution plan. startParameter.taskNames holds raw input, but the actual graph (with dependencies) only exists at whenReady.
  • Why does modern Gradle prefer lazy Provider/Property over these hooks?
    Lazy wiring defers value computation declaratively without imperative lifecycle callbacks, keeping builds configuration-cache compatible and avoiding ordering bugs.

afterEvaluate is each musician finishing tuning their own instrument; whenReady is the conductor seeing the full assembled orchestra and final setlist right before the downbeat.

saying these in an interview costs you the question

  • Saying afterEvaluate gives access to the task graph — it runs before the graph is built.
  • Using whenReady for per-project property finalization when afterEvaluate (or lazy wiring) is the right tool.
  • Treating startParameter.taskNames as the resolved execution plan.

context