A large multi-project build has a slow configuration phase. How would you diagnose and reduce configuration-phase time using configuration avoidance?
answer
- --profile / --scan to measure
- help isolates configuration cost
- audit convention plugins for eager calls
- register/named/configureEach + flatMap
- Configuration Cache skips the phase
basics
~10 sProfile with --profile or a build scan to see configuration time, find eager task creation/realization (create, getByName, all, withType{}), convert to register/named/configureEach, and wire by provider. Consider the Configuration Cache.
solid answer
~40 sFirst measure: `./gradlew help --profile` or a build scan shows configuration vs execution time per project and flags eager task realization. The usual culprits in a big build are plugins or convention scripts using `tasks.create`, `tasks.all { }`, eager `withType(T) { }`, and `getByName`, which realize every task on every invocation. Remediation: migrate to `tasks.register`/`named`/`withType(T).configureEach { }`; replace `.get()`-based wiring with provider `map`/`flatMap`; avoid expensive work (file I/O, `configurations.resolve`, network) at configuration time. Confirm by re-profiling — configuration time should drop because unused tasks are no longer configured. The end-state lever is the **Configuration Cache**, which serializes the configured task graph and skips the configuration phase entirely on subsequent compatible runs; configuration avoidance is the prerequisite hygiene that makes the build cache-compatible and the cached graph cheaper to (re)build when invalidated.
code
bash · 9 lines# isolate & measure configuration-phase cost
./gradlew help --profile
# open build/reports/profile/*.html -> 'Configuration' column
# richer breakdown incl. eagerly created tasks
./gradlew :app:compileJava --scan
# end-state: skip configuration on cache hits
./gradlew build --configuration-cachego deeper
Know that --profile shows configuration vs execution time and that register beats create.
Identify eager APIs and convert them; mention help --profile to isolate configuration cost.
Drive an end-to-end diagnose→migrate→re-measure loop, prioritize convention plugins, and connect to the Configuration Cache.
Set org-wide guardrails (CC in CI, banned eager accessors, scan tracking) and make configuration-time budgets part of build governance.
## Step 1 — measure, don't guess Configuration runs every build, so a slow configuration phase taxes every invocation including `help`. Quantify it: - **`./gradlew <task> --profile`** writes an HTML report under `build/reports/profile/` splitting *configuration* vs *task execution* time, per project. - **Build Scan** (`--scan`) gives a `Performance` page with a configuration breakdown and a list of tasks created eagerly. - Running **`./gradlew help`** isolates configuration cost (it does almost no execution), so its wall-clock time is roughly your configuration overhead. ## Step 2 — find eager realization Audit build logic (especially shared **convention plugins**, since one eager call there multiplies across all projects) for: | Eager (bad) | Lazy (good) | |---|---| | `tasks.create("x")` | `tasks.register("x")` | | `tasks.getByName("x")` / `tasks["x"]` | `tasks.named("x")` | | `tasks.all { }` | `tasks.configureEach { }` | | `tasks.withType(T) { }` | `tasks.withType(T).configureEach { }` | | `provider.get()` in config | `provider.map/flatMap` | Also flag expensive work done unconditionally at configuration time: reading files, resolving dependency `configurations` (`configuration.resolve()` / `.files`), shelling out, or network calls. Move these into task actions or lazy providers. ## Step 3 — migrate and re-measure Convert the hot spots, then re-run `--profile`/`--scan`. Because unused tasks are no longer realized, configuration time should fall, especially for narrow commands like `:app:compileJava` that previously paid for configuring test/check/publish tasks across every subproject. ```kotlin // convention plugin: keep it lazy so it doesn't realize every task in every project plugins.withType<JavaPlugin> { tasks.withType<Test>().configureEach { maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1) } } ``` ## Step 4 — adopt the Configuration Cache The Configuration Cache (`org.gradle.configuration-cache=true`) serializes the *result* of the configuration phase. On a cache hit Gradle **skips configuration entirely** and goes straight to execution. It requires laziness and CC-compatible code (no live `Project` access at execution time), so the avoidance work above is a prerequisite. Combined, you get: cheaper configuration when it must run, and no configuration at all on cache hits. ## Step 5 — guardrails Prevent regressions: forbid eager accessors in shared plugins via code review or a static check, enable the Configuration Cache in CI so incompatibilities fail fast, and track configuration time in build scans over time.
- Why focus first on convention/shared plugins when fixing configuration time?They run in every subproject, so a single eager call (e.g. tasks.all {} or getByName) is multiplied across the whole build, making them the highest-leverage fix.
- How does the Configuration Cache relate to configuration avoidance?They're complementary: avoidance reduces cost within a configuration phase that still runs, while the Configuration Cache stores the configured graph to skip the phase entirely on hits. Avoidance/laziness is a prerequisite for cache compatibility.
- What expensive non-task work can also slow configuration?Resolving dependency configurations (.resolve()/.files), reading files, network calls, or running external processes at configuration time. Defer these into task actions or lazy providers.
saying these in an interview costs you the question
- Optimizing without measuring first (--profile/--scan).
- Assuming the Configuration Cache alone fixes a slow configuration phase without fixing eager realization.
- Resolving dependency configurations eagerly at configuration time.