skip to content

Cross-Project Configuration Ordering

Why project configuration order is undefined, what evaluationDependsOn forces, and why eagerly reading another project's state is fragile. Interviewers ask because lazy providers are the answer they are listening for.

on this pageshow

questions

5

In a Gradle multi-project build, what determines the order in which projects are configured (evaluated), and is that order guaranteed?

level: middleimportance: must knowfreq 55%

answer

  1. root configured first
  2. order among subprojects undefined
  3. not include order, not alphabetical
  4. evaluationDependsOn to force
  5. prefer lazy Providers

basics

~10 s

Gradle configures projects in an order it chooses; by default the root is evaluated first, but the order among other projects is not something you should rely on unless you force it.

solid answer

~40 s

Gradle has two phases: configuration (it runs every project's build script to build the task graph) then execution. During configuration Gradle evaluates the root project first, then the others. The exact order among subprojects is **undefined** — you must not assume `:lib` is configured before `:app` just because of alphabetical order or `include` order. If a project's configuration genuinely needs another project to be configured first, you declare it explicitly with `evaluationDependsOn(':lib')` (or `evaluationDependsOnChildren()` in a parent). The much better practice, though, is to avoid the dependency entirely by using **lazy** `Provider`/`Property` values so the other project's state is read at execution time, not configuration time.

code

kotlin · 11 lines
kotlin
// :app/build.gradle.kts
// Force :lib to be configured first IF you must read its config state
evaluationDependsOn(":lib")

val libVersion = project(":lib").version  // now safe at config time

// Better: avoid the ordering need with a lazy provider
val libJar = project(":lib").tasks.named("jar")
tasks.register("useLib") {
    inputs.files(libJar)  // lazy wiring, no evaluation-order assumption
}

go deeper

for a junior

Know that there are configuration and execution phases and that you can't assume one project is set up before another.

for a middle

Explain root-first, undefined order among the rest, and that evaluationDependsOn forces it when needed.

for a senior

Frame the eager-read as a design smell and reach for lazy Providers; mention configuration-cache implications.

for a principal

Set a team convention banning eager cross-project state reads; treat providers + isolated projects as the architecture for scalable builds.

## Build phases A Gradle build runs in three phases: 1. **Initialization** — `settings.gradle.kts` runs, `include(...)` declares which projects exist, the `Project` objects are created (but their build scripts have not run yet). 2. **Configuration** — Gradle executes *every* project's `build.gradle.kts` top-to-bottom to register tasks and wire up the task graph. This is where 'evaluation order' matters. 3. **Execution** — the selected tasks actually run. ## Why order is 'undefined' During configuration Gradle evaluates the **root project first**, then walks the remaining projects. The order among non-root projects is an implementation detail and is **not guaranteed**. It is *not* the `include` order and *not* guaranteed alphabetical — relying on it is a latent bug. Code like this in `:app` is fragile: ```kotlin // :app/build.gradle.kts — BAD: reads :lib eagerly at configuration time val libVersion = project(":lib").version // :lib may not be configured yet! ``` If `:lib` happens to be evaluated after `:app`, `version` is still the default, and you get a silent wrong value. ## Forcing order: evaluationDependsOn When one project's *configuration* truly must observe another's configured state, declare it: - `evaluationDependsOn(":lib")` — guarantees `:lib` is fully evaluated before the current project's script finishes evaluating. - `evaluationDependsOnChildren()` — in a parent project, evaluates all child projects first. These only affect **configuration ordering**, not task execution ordering (that's `dependsOn`/`mustRunAfter`). ## The better fix: laziness Most cross-project reads should never happen eagerly. Use the **Provider API** (`Provider<T>`, `Property<T>`). A `Provider` is a lazy value resolved on demand — typically at execution time, by which point every project is configured. Wiring an output of `:lib` into an input of `:app` via providers means you never care about evaluation order, and it plays nicely with the **configuration cache**, which forbids reading another project's mutable state at execution time. Eager cross-project access is exactly what the configuration cache flags as a violation.

  • Does evaluationDependsOn affect the order tasks execute in?
    No. It only forces configuration (evaluation) ordering. Task execution order is controlled by dependsOn / mustRunAfter / shouldRunAfter and the dependency graph.
  • Why doesn't include order in settings.gradle.kts guarantee configuration order?
    include only registers that the projects exist during initialization. Configuration order is decided separately by Gradle and is an unspecified implementation detail.

saying these in an interview costs you the question

  • Claiming subprojects are always configured in alphabetical or include order.
  • Saying evaluationDependsOn controls task execution order.
  • Treating eager project(':lib').version reads as safe.

context

open as a page

Why is eagerly reading another project's state at configuration time fragile, and how do lazy Providers fix it?

level: seniorimportance: must knowfreq 50%

basics

~10 s

Reading another project at configuration time can run before that project is configured, giving stale values. Lazy Providers defer the read until execution, when everything is configured, so order no longer matters.

open as a page

How do allprojects { } and subprojects { } interact with configuration ordering, and why can configuration injection be deferred safely?

level: middleimportance: should knowfreq 30%

basics

~10 s

allprojects/subprojects in the root register configuration that Gradle applies to each project when that project is evaluated. It's deferred per project, so it doesn't depend on a fixed evaluation order.

open as a page

A property read from another subproject is sometimes correct and sometimes the default value across builds. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 28%

basics

~10 s

It's almost certainly an evaluation-order bug: you're reading the other project eagerly at configuration time and it isn't always configured first. Replace the eager read with a lazy Provider, or force order with evaluationDependsOn.

open as a page

When would you reach for evaluationDependsOn(':lib') versus evaluationDependsOnChildren(), and what are the risks?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Use evaluationDependsOn(':lib') in one project to force a specific other project to be configured first; use evaluationDependsOnChildren() in a parent to configure all its children first. Both can mask design issues and slow configuration.

open as a page