Why is resolving dependencies during the configuration phase a performance problem, and how would you detect it?
answer
- configuration phase runs every build
- resolving config in script body = per-build tax
- triggers: .files/.resolve()/.incoming at config time
- --profile Dependency Resolution slice
- scan flags config resolved at configuration time
basics
~20 sConfiguration runs on every build, so resolving a dependency configuration there (e.g. iterating configurations.x.files in a build-script block) forces network/metadata work each time. Detect it via --profile's Dependency Resolution section or a build scan, which attribute that time to configuration.
solid answer
~50 sDependency resolution is expensive: it reads metadata, may hit the network, and computes a full dependency graph. Because the **configuration phase runs on essentially every invocation**, triggering resolution there (for example, eagerly accessing `configuration.files`, `configuration.resolve()`, or `configuration.incoming.artifacts` inside the build script rather than inside a task action) makes that cost recur on every build — even `gradle help`. The correct place is at execution time, inside a task input/action, so it only happens when actually needed and benefits from up-to-date checks. To detect it, profile the build: in the `--profile` HTML report the **Dependency Resolution** bucket and an inflated Configuration total are the tell, and a **build scan** explicitly flags configurations resolved during the configuration phase in its performance/timeline view. Pairing a slow `gradle help --profile` with a large resolution slice is a strong signal.
code
kotlin · 9 lines// ANTI-PATTERN: forces resolution during configuration (runs every build)
val classpath = configurations.runtimeClasspath.get().files
println("resolved at config time: ${classpath.size}") // bad
// Better: keep it lazy so resolution happens at execution time
tasks.register("report") {
val files = configurations.runtimeClasspath // not resolved yet
doLast { println(files.get().files.size) } // resolved only when task runs
}go deeper
Know that resolving dependencies during configuration makes every build slower.
Identify the API triggers and use --profile/scan to confirm resolution is happening at configuration time.
Explain the lazy-input remedy and how it restores up-to-date/cacheable behavior, and audit plugins for the anti-pattern.
Set conventions/lint that forbid configuration-time resolution across shared plugins so the whole org's builds stay cheap to configure.
## Why this is a trap Resolving a Gradle `Configuration` (the dependency-set kind, e.g. `compileClasspath`) computes the full transitive dependency graph: it downloads/reads `.module`/POM metadata, applies conflict resolution and version constraints, and locates artifacts. That work is non-trivial and may touch the network. The phase rule from the broader topic applies hard here: **configuration time is paid on every build, unavoidably.** So if a plugin or build script *resolves* a configuration while the script is being evaluated, every single developer command — including no-op ones — eats that cost. Common accidental triggers: - Reading `configurations.runtimeClasspath.files` (or `.asPath`, `.resolve()`, `.incoming.artifacts.artifactFiles`) directly in the script body or in a `tasks.create`/eagerly-realized configuration block. - Computing a version or classpath string at configuration time to plug into another task's property eagerly. ## The fix in principle (not the focus here) The remedy is to defer resolution to execution time — wire the configuration into a task as a lazy input (`Provider`/`FileCollection`) so it resolves only when that task runs, and benefits from up-to-date checks and the build cache. The lazy-task mechanics belong to the configuration-avoidance topic; here we care about *spotting* the symptom. ## How to detect it 1. **`--profile`**: the report has a dedicated **Dependency Resolution** breakdown plus the **Configuration** total. If resolution time is large and the build is slow even for `gradle help --profile`, resolution is happening at configuration time. 2. **Build scan (`--scan`)**: scans show a phase timeline and a **Performance** section that calls out configurations resolved during project configuration, often naming the specific configuration and project. This is the most precise pointer. 3. **Eyeball test**: if turning on `--offline` makes a near-no-op build dramatically faster, something is resolving (and hitting the network) during configuration. ## Why deferring helps Moving resolution to execution means it runs only for the tasks that need it, can be skipped when the consuming task is up-to-date, and can be cached. That converts an unconditional per-build tax into avoidable, cacheable work. ```text Symptom: gradle help --profile slow, large 'Dependency Resolution' slice Scan: 'Performance' tab -> 'Settings and suggestions' / configurations resolved during configuration phase ```
- Which API calls typically trigger eager resolution at configuration time?Accessing `configuration.files`, `.asPath`, `.resolve()`, `.singleFile`, or `.incoming.artifacts.artifactFiles` in the script body or an eagerly-realized block forces the configuration to resolve immediately.
- How does deferring resolution to execution time also help correctness, not just speed?At execution time the resolved files become task inputs, so up-to-date checks and the build cache can skip the consuming task when nothing changed — you get both speed and reproducibility.
It's like phoning the warehouse to count stock every time you merely open the store, instead of only when a customer actually places an order.
saying these in an interview costs you the question
- Claiming the build cache fixes configuration-time resolution — caching only applies to executed task outputs.
- Believing dependency resolution is cheap; for large graphs it's a major, recurring configuration cost.
- Confusing a resolvable `Configuration` (dependency bucket) with the configuration *phase*.