When would you use gradle.projectsEvaluated, and how does it solve cross-project configuration ordering problems?
answer
- fires once, after ALL projects evaluated
- cross-project ordering guarantee
- aggregate tasks across modules
- afterEvaluate lacks cross-project safety
- prefer lazy/BuildService alternatives
basics
~20 sgradle.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.
solid answer
~50 s`gradle.projectsEvaluated { gradle -> }` is a build-wide hook that fires a **single time**, after the build scripts of **all** projects have finished evaluating — i.e. at the end of the whole configuration phase. Its purpose is **cross-project ordering**: if subproject A needs to read a value that subproject B declares in its own build script, a plain `afterEvaluate` in A may run before B has been evaluated, so the value isn't there yet. Waiting until `projectsEvaluated` guarantees every script has run, so you can safely inspect the fully-configured project graph. Typical uses: aggregating reports across modules, wiring up dependencies based on every module's declared attributes, or generating an aggregate task. The modern alternative is to model the relationship lazily (Providers, shared `BuildService`, or cross-project task dependencies) so you don't depend on evaluation order at all. Like other configuration hooks it's eager and constrained under the configuration cache.
code
kotlin · 6 linesgradle.projectsEvaluated {
val javaProjects = rootProject.subprojects.filter { it.plugins.hasPlugin("java") }
rootProject.tasks.register("allTests") {
dependsOn(javaProjects.map { "${it.path}:test" })
}
}go deeper
Know it runs once after all projects are evaluated.
Explain it solves cross-project ordering vs afterEvaluate and is used for aggregation across modules.
Position it precisely in the lifecycle, give a concrete aggregate-task example, and contrast with afterEvaluate/afterProject timing.
Argue for lazy/BuildService/task-dependency approaches over ordering hooks, and address configuration-cache constraints on cross-project access in large multi-module builds.
## The ordering problem it solves During configuration, Gradle evaluates each project's build script. With parallel-friendly or on-demand configuration the **order** in which projects are evaluated is not guaranteed to match your dependency expectations. So if subproject `:app` wants to read something `:lib` set in `:lib`'s build script, using `:app`'s own `afterEvaluate` is unsafe — `:lib` might not be evaluated yet. `gradle.projectsEvaluated { }` fixes this by firing **once**, only after **all** projects have been evaluated. At that point every build script has run, so the entire configured project graph is consistent and safe to read. ## Where it sits in the lifecycle Roughly per build: 1. Init phase — settings evaluated, projects created. 2. Each project: `beforeProject` → evaluate script → `afterEvaluate`/`afterProject`. 3. **`projectsEvaluated`** — fires once, all scripts now done. 4. Task graph is finalized, then execution begins. ## Typical use cases - **Aggregation tasks**: build a `mergeReports` task that collects outputs declared by every module — you need to know all modules' declarations first. - **Cross-module wiring**: e.g. find every project that applied a given plugin and wire them together. ```kotlin // settings.gradle.kts or root build.gradle.kts gradle.projectsEvaluated { val testedProjects = rootProject.subprojects.filter { it.plugins.hasPlugin("java") } rootProject.tasks.register("allTests") { dependsOn(testedProjects.map { "${it.path}:test" }) } } ``` ## projectsEvaluated vs afterEvaluate vs afterProject - `project.afterEvaluate` — one project, after its own script. **No** cross-project guarantee. - `gradle.afterProject` — per project, after each script, broadcast. Still per-project timing. - `gradle.projectsEvaluated` — **once**, after **all** scripts. The only one that guarantees the whole graph is configured. ## Modern alternatives (preferred) Depending on evaluation order is fragile. Prefer: - **Lazy `Provider`/`Property`** wiring so values resolve at execution time regardless of order. - **`BuildService`** for shared, ordering-independent state. - **Cross-project task dependencies** (`dependsOn(project(":lib").tasks.named(...))`) which Gradle resolves correctly without manual hooks. Use `projectsEvaluated` when you genuinely must inspect the static configuration of all projects at once and no lazy formulation exists. Note it is eager configuration-phase code and is restricted under the **configuration cache**, which forbids reaching across the project graph at execution time.
- Why is using afterEvaluate in project A unsafe when A depends on a value set in project B's script?Evaluation order across projects isn't guaranteed, so A's afterEvaluate can run before B's script has executed, leaving B's value unset. projectsEvaluated waits for all scripts.
- What modern mechanisms let you avoid depending on projectsEvaluated entirely?Lazy Provider/Property wiring, a shared BuildService, and explicit cross-project task dependencies — all of which resolve correctly without relying on configuration ordering.
- How often does projectsEvaluated fire per build?Exactly once, after the configuration phase completes for all projects.
saying these in an interview costs you the question
- Saying projectsEvaluated fires per project — it fires once for the whole build.
- Recommending it as a default for cross-module wiring instead of lazy Providers/task dependencies.
- Assuming evaluation order matches the dependency graph — it does not, which is the whole reason this hook exists.