skip to content

Why is resolving dependencies during the configuration phase a performance problem, and how would you detect it?

level: middleimportance: should knowfreq 45%

answer

  1. configuration phase runs every build
  2. resolving config in script body = per-build tax
  3. triggers: .files/.resolve()/.incoming at config time
  4. --profile Dependency Resolution slice
  5. scan flags config resolved at configuration time

basics

~20 s

Configuration 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 s

Dependency 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
kotlin
// 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

for a junior

Know that resolving dependencies during configuration makes every build slower.

for a middle

Identify the API triggers and use --profile/scan to confirm resolution is happening at configuration time.

for a senior

Explain the lazy-input remedy and how it restores up-to-date/cacheable behavior, and audit plugins for the anti-pattern.

for a principal

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*.

context