skip to content

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