skip to content

What is project.afterEvaluate used for, and why would you defer configuration logic into it instead of writing it directly in the build script?

level: middleimportance: must knowfreq 70%

answer

  1. runs after build script evaluated
  2. fixes empty-extension-at-apply
  3. configuration phase tail
  4. prefer lazy Provider/Property
  5. registration-order callbacks

basics

~20 s

afterEvaluate registers a callback that runs after a project's build script has finished being evaluated. You use it when your logic depends on values (like extension properties) that are only set later in that script.

solid answer

~50 s

Gradle builds run in two main phases: configuration and execution. `project.afterEvaluate { }` schedules a closure to run at the very end of the **configuration** phase for that specific project — after its entire build script has been evaluated. The classic use case is a plugin reading values a user supplied via an extension: when the plugin's `apply()` runs, the extension is empty, because the `myExt { ... }` block in the build script hasn't executed yet. Deferring reads into `afterEvaluate` lets the user's configuration win. In modern Gradle the preferred alternative is to wire lazy `Provider`/`Property` values so you never need to read eagerly at all, but `afterEvaluate` remains the escape hatch when a non-lazy API forces an eager read. Order matters: multiple `afterEvaluate` blocks run in registration order, and adding one from inside another may not fire.

code

kotlin · 9 lines
kotlin
class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val ext = project.extensions.create("greeting", GreetingExtension::class.java)
        project.afterEvaluate {
            // ext is now fully configured by the build script
            println("greeting = ${ext.message}")
        }
    }
}

go deeper

for a junior

Know that it runs a block after the build script is read, and is used to read configuration that's set later.

for a middle

Explain the empty-extension-at-apply problem and that it fires at the end of the configuration phase for one project.

for a senior

Contrast with lazy Provider/Property wiring as the preferred approach; mention ordering and the nested-registration gotcha.

for a principal

Frame afterEvaluate as a legacy escape hatch; discuss its tension with configuration cache and how plugin APIs should expose lazy properties so consumers never need it.

## Build phases first Every Gradle build runs in three phases: 1. **Initialization** — Gradle decides which projects participate and creates a `Project` object for each. 2. **Configuration** — every participating project's build script is evaluated top-to-bottom; tasks are *registered/configured* but not run. 3. **Execution** — the selected tasks actually run. `project.afterEvaluate { ... }` is a **lifecycle hook** that fires at the end of the configuration phase **for one project** — specifically right after that project's build script has been fully evaluated. ## Why it exists: the empty-extension problem When a plugin is applied, its `apply(project)` method runs immediately. If the build script later configures an extension, that configuration has not happened yet at apply time: ```kotlin class GreetingPlugin : Plugin<Project> { override fun apply(project: Project) { val ext = project.extensions.create("greeting", GreetingExtension::class.java) // ext.message is still the DEFAULT here — the build script's greeting { } block runs LATER project.afterEvaluate { println("Configured message = ${ext.message}") // now reflects user input } } } ``` The build script's `greeting { message = "hi" }` block runs as part of evaluating that script — i.e. *after* `apply()`. So reading `ext.message` eagerly inside `apply()` gives the default; reading it inside `afterEvaluate` gives the user's value. ## The modern, preferred way: lazy Providers Reaching for `afterEvaluate` is a code smell in new code. The idiomatic Gradle 7/8 approach is **lazy configuration** with `Property<T>`/`Provider<T>`: the plugin wires the extension property into the task without reading it, and the value is only resolved at execution time — by which point the user's configuration is in place. ```kotlin val ext = project.extensions.create("greeting", GreetingExtension::class.java) project.tasks.register("greet", GreetTask::class.java) { it.message.set(ext.message) // Provider wiring — no eager read, no afterEvaluate needed } ``` Use `afterEvaluate` only when a non-lazy API forces an eager read (e.g. you must call a third-party method that takes a plain `String` at configuration time). ## Ordering & gotchas - Multiple `afterEvaluate` callbacks run in **registration order**. - An `afterEvaluate` block registered from *inside* another `afterEvaluate` may be skipped — by then the hook may already be closed. - It runs **once per project**, not per build. - It does **not** help with cross-project ordering; for that you want `gradle.projectsEvaluated`. - It still runs during the configuration phase, so it counts toward configuration time and is sensitive to **configuration cache** rules.

  • What is the modern alternative that often removes the need for afterEvaluate?
    Lazy configuration: wire extension Property/Provider values directly into tasks with .set(...). The value is resolved at execution time, after the user's config is applied, so no deferred read is needed.
  • If you register an afterEvaluate block from inside another afterEvaluate, will it run?
    Not reliably — the hook may already be closed by then, so the newly added callback can be silently skipped.

saying these in an interview costs you the question

  • Saying afterEvaluate runs during the execution phase — it runs at the end of configuration.
  • Claiming afterEvaluate is the recommended default for reading extensions — lazy Providers are preferred.
  • Confusing it with gradle.afterProject (which is the same hook expressed at the Gradle level for all projects).

context