skip to content

Project Evaluation Hooks

afterEvaluate, beforeProject and afterProject, and projectsEvaluated for running logic once a project's script has been configured. Interviewers single out afterEvaluate, which is both indispensable and a recognized smell.

on this pageshow

questions

5

At a high level, what is the build configuration phase, and where do project evaluation hooks like afterEvaluate fit within it?

level: juniorimportance: must knowfreq 55%

answer

  1. three phases: init, config, execution
  2. configuration registers tasks, no actions
  3. beforeEvaluate before script, afterEvaluate after
  4. hooks finish before execution
  5. apply() is early in configuration

basics

~20 s

The configuration phase is when Gradle reads every project's build script and registers tasks (but doesn't run them). Project evaluation hooks like afterEvaluate run at the tail of this phase, right after a project's script has been read.

solid answer

~40 s

A Gradle build has three phases: **initialization** (decide which projects participate, create Project objects), **configuration** (evaluate each project's build script and build the task graph — tasks are registered/configured, not executed), and **execution** (run the requested tasks). Project evaluation hooks belong to the **configuration** phase: `project.beforeEvaluate` fires just before a project's script is evaluated, and `project.afterEvaluate` fires just after. They let plugins or scripts run logic that depends on the script having been (or about to be) processed — for example, reading an extension that the user configured. They run during configuration, so they happen before any task action executes. Knowing this placement explains why reading user-set values is safe in afterEvaluate but not at plugin-apply time.

go deeper

for a junior

Name the three phases and state that afterEvaluate runs at the end of configuration for a project.

for a middle

Explain the before/after-script ordering and why afterEvaluate is the safe place to read user-set values.

for a senior

Connect placement to why apply-time reads fail and how configuration-phase cost matters for build performance.

for a principal

Use the phase model to reason about configuration-time budget, configuration cache, and where to minimize eager work across a large build.

## The three build phases Every Gradle build proceeds through three phases: 1. **Initialization** — Gradle evaluates `settings.gradle(.kts)`, decides which projects are part of the build, and creates a `Project` instance for each. 2. **Configuration** — Gradle evaluates **every** participating project's build script top-to-bottom. This **registers and configures tasks** and builds the **task (dependency) graph**. Crucially, no task *actions* run yet — declaring `tasks.register("foo") { ... }` configures the task but does not execute it. 3. **Execution** — Gradle runs the tasks you asked for (and their dependencies), in dependency order. Task `@TaskAction` methods / `doLast`/`doFirst` blocks run here. ## Where evaluation hooks live Project **evaluation hooks** are part of the **configuration** phase: - `project.beforeEvaluate { }` — runs immediately **before** that project's build script is evaluated. - `project.afterEvaluate { }` — runs immediately **after** that project's build script has been evaluated. So for one project the order is: `beforeEvaluate` → evaluate the build script → `afterEvaluate`. ```kotlin project.beforeEvaluate { println("about to read this project's script") } project.afterEvaluate { println("this project's script is fully read") } ``` ## Why the placement matters Because these hooks run during configuration — and `afterEvaluate` specifically runs *after* the script — they are the right place to read values that the build script set (like extension properties). At **plugin-apply** time (earlier in configuration) those values aren't set yet. And because they all run in configuration, they finish **before** any task executes, so they cannot depend on task outputs. ## Quick mental model - Initialization: *which* projects. - Configuration: *what* tasks exist and how they depend on each other (evaluation hooks here). - Execution: *do* the work.

  • Do task actions run during the configuration phase?
    No — configuration only registers and configures tasks and builds the graph. Task actions (doLast/@TaskAction) run later, in the execution phase.
  • Which runs first, a plugin's apply() or that project's afterEvaluate?
    apply() runs first (early in configuration); afterEvaluate fires later, after the whole build script has been evaluated.

Configuration is like setting up a recipe and ordering ingredients (deciding the steps) — execution is actually cooking. afterEvaluate is the moment right after you've finished reading the whole recipe card, before you turn on the stove.

saying these in an interview costs you the question

  • Saying configuration runs task actions — it only configures the task graph.
  • Placing afterEvaluate in the execution phase.
  • Thinking initialization evaluates build scripts — it evaluates settings and creates Project objects.

context

open as a page

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%

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.

open as a page

Explain gradle.beforeProject and gradle.afterProject. How do they differ from a project calling its own afterEvaluate?

level: middleimportance: should knowfreq 45%

basics

~10 s

gradle.beforeProject and gradle.afterProject register callbacks that fire around the evaluation of EVERY project in the build, receiving the project as an argument. afterEvaluate is scoped to the single project that registers it.

open as a page

Why is afterEvaluate considered problematic with the configuration cache, and what should you do instead?

level: seniorimportance: should knowfreq 38%

basics

~20 s

afterEvaluate runs eager configuration logic that often captures live build state. The configuration cache stores the configured graph and skips re-running configuration, so eager hooks become fragile. Prefer lazy Provider/Property wiring that resolves at execution time.

open as a page

When would you use gradle.projectsEvaluated, and how does it solve cross-project configuration ordering problems?

level: seniorimportance: should knowfreq 40%

basics

~20 s

gradle.projectsEvaluated fires once, after EVERY project in the build has been evaluated. Use it when configuration in one project depends on settings declared in another, so you must wait until all build scripts are read.

open as a page