Why does Gradle configure every project on every build by default, and what problems does that cause?
answer
- all projects configured for complete task model
- cross-project deps need everything wired
- cost scales with module count
- config cache skips the phase
- lazy APIs keep each script cheap
basics
~20 sBy default Gradle evaluates all participating projects' build scripts so it has a complete, consistent task model and can resolve cross-project dependencies. The cost is that even unrelated subprojects are configured on every command, slowing builds in large repos.
solid answer
~40 sGradle's configuration phase, by default, evaluates **every** project that initialization included — not just the one owning your requested task. The reason is correctness and cross-project wiring: a task in one subproject can depend on tasks or outputs in another (`project(":a").tasks`, `dependsOn`, configuration dependencies), so Gradle needs the *whole* task model resolved before it can build a correct task graph. The downside is **configuration time cost**: in a 200-module monorepo, running a single test still pays to evaluate all 200 build scripts. Anything heavy at configuration time (eager `create`, file scans, dependency resolution, network calls) is multiplied across modules and paid on every invocation. Mitigations include configuration avoidance (lazy `register`/Provider APIs), and the configuration cache, which stores the configured task graph and skips re-running the configuration phase when inputs are unchanged.
code
bash · 4 lines# Even a single-module test configures all modules by default
gradle :app:test
# Enable the configuration cache to skip re-configuring on unchanged inputs
gradle :app:test --configuration-cachego deeper
Know that by default all projects get configured, not just the one you target.
Explain the correctness reason (cross-project wiring / complete task model) and the cost, and name lazy APIs as mitigation.
Add the configuration cache as the structural fix and distinguish it from the build cache; reason about where heavy work belongs.
Set monorepo policy: enforce configuration cache compatibility and lazy conventions so configuration time stays bounded as modules grow.
## The default behavior After initialization selects the participating projects, the configuration phase evaluates **all** of their build scripts — not a subset chosen by the requested task. If you run `gradle :app:test` in a multi-project build, Gradle still configures `:lib-a`, `:lib-b`, and every other module by default. ## Why Gradle does this Gradle must produce a **correct and complete task model** before it can build the execution graph: - **Cross-project dependencies.** `:app`'s tasks may `dependsOn` tasks in `:lib-a`, or `:app` may depend on `:lib-a`'s produced artifacts via a project dependency. To wire these, Gradle needs `:lib-a` configured so those tasks/configurations exist. - **Global consistency.** Plugins and conventions can register tasks reactively across projects; resolving the model partially could miss wiring and produce an incorrect graph. Eager whole-graph configuration is the conservative way to guarantee the model is complete. ## The problems it causes 1. **Configuration time scales with module count.** Every build pays to evaluate every script, even for modules unrelated to the command. In large monorepos this dominates the time of fast commands like `help` or a single test. 2. **Heavy configuration-time work is multiplied.** Eager task creation (`create`), filesystem scans, premature dependency resolution, or network calls placed in a script body run for *all* modules, *every* invocation. ## Mitigations - **Configuration avoidance:** declare tasks with `tasks.register` (lazy) instead of `create`, use `Provider`/`Property` and `configureEach` so unused tasks are never realized. This keeps each project's configuration cheap even if all scripts are evaluated. - **Configuration cache:** Gradle can serialize the fully-configured task graph keyed on build inputs; on a subsequent build with unchanged inputs it **skips the configuration phase entirely** and reuses the cached graph. This directly attacks the "configure everything every time" cost. - **Keep script bodies pure:** move work into task actions (execution time) or lazy providers so it isn't done eagerly during configuration. ```kotlin // Bad: runs for every module on every build val files = file("src").walkTopDown().toList() // eager I/O at config time // Better: defer into a provider / task action so it isn't paid eagerly val files = providers.provider { file("src").walkTopDown().toList() } ``` ## Summary Default full configuration buys a correct, complete task model at the cost of paying configuration for every module on every build. The configuration cache and configuration-avoidance APIs are how you reclaim that cost without sacrificing correctness.
- How does the configuration cache change this default behavior?It serializes the configured task graph keyed on build inputs. On a later build with unchanged inputs Gradle reuses the cached graph and skips the configuration phase entirely, so it doesn't re-evaluate every script.
- If all projects are configured anyway, why bother with `register` over `create`?Even though every *script* is evaluated, lazy registration means the per-task creation and configuration work is deferred until a task is realized. So configuring a script that registers 50 unused tasks stays cheap.
saying these in an interview costs you the question
- Saying Gradle only configures the project owning the requested task by default.
- Claiming cross-project dependencies work without configuring the depended-on project.
- Confusing configuration cache (skips configuration) with build cache (reuses task outputs).