Why is eagerly reading another project's state at configuration time fragile, and how do lazy Providers fix it?
answer
- eager read = runs during config
- undefined order => stale defaults
- forces other project to configure
- config-cache violation
- Provider/Property = lazy, dependency-carrying
basics
~10 sReading another project at configuration time can run before that project is configured, giving stale values. Lazy Providers defer the read until execution, when everything is configured, so order no longer matters.
solid answer
~40 sEager access — e.g. `project(":lib").extensions.getByType(...)` or reading its `version`/extension at the top of your build script — runs during your configuration phase. Because subproject evaluation order is undefined, `:lib` may not be configured yet, so you read defaults and get a silent wrong result; it also forces `:lib` to configure even when the task isn't requested, hurting configuration time, and it's an explicit **configuration-cache** violation (you can't capture another project's mutable state). The fix is the **Provider API**: model the value as a `Provider<T>`/`Property<T>` and wire it into task inputs with `inputs.files(...)`, `task.flatMap { ... }`, etc. A `Provider` is resolved lazily — at execution, or when the graph is realized — by which point all projects are configured. This removes the ordering dependency, keeps configuration lazy, and is configuration-cache compatible.
code
kotlin · 9 lines// Lazy cross-project wiring — no evaluation-order assumption
val libVersion: Provider<String> =
providers.provider { project(":lib").version.toString() }
tasks.register("writeVersion") {
val out = layout.buildDirectory.file("version.txt")
outputs.file(out)
doLast { out.get().asFile.writeText(libVersion.get()) } // resolved at execution
}go deeper
Recognize that reading another project's value too early can be wrong; don't need the provider internals.
Contrast eager vs lazy and name Provider/Property as the lazy tools.
Explain dependency-carrying providers, eager-configuration cost, and configuration-cache compatibility.
Mandate provider-based wiring and isolated projects across the org; treat eager cross-project reads as a reviewable anti-pattern.
## What 'eager' means here **Eager** code runs immediately while your `build.gradle.kts` is being evaluated. Examples that reach into another project: ```kotlin val v = project(":lib").version // reads now val ext = project(":lib").extensions.getByType<MyExt>() // reads now val out = project(":lib").tasks.getByName("jar") // realizes the task now ``` Three problems: 1. **Ordering hazard** — subproject evaluation order is undefined, so `:lib` may not be configured yet; you silently capture a default. 2. **Eager configuration** — touching `:lib` forces it to configure even if no task there is needed, slowing every build (the opposite of Gradle's lazy/`tasks.register` philosophy). 3. **Configuration cache violation** — the configuration cache serializes the task graph and forbids a task action from reading another project's mutable model; eager cross-project reads break it. ## The Provider API A `Provider<T>` is a **lazy** container: it holds a recipe for a value, not the value. `Property<T>` is a settable provider. Key traits: - Resolved **on demand** (often at execution), via `provider.get()`. - Composable: `map`, `flatMap`, `zip` build derived providers without resolving. - Carry **task dependencies**: a provider sourced from a task output makes the consuming task depend on the producing task automatically. ## Wiring across projects, the right way ```kotlin // :app/build.gradle.kts val libJar = project(":lib").tasks.named<Jar>("jar") tasks.register<Copy>("bundle") { // lazy: :lib's jar output, resolved at execution, dependency wired for free from(libJar.flatMap { it.archiveFile }) into(layout.buildDirectory.dir("bundle")) } ``` Here nothing about `:lib` is read at configuration time except the *named* task reference (which doesn't realize it). Evaluation order is irrelevant, configuration stays lazy, and the configuration cache is happy. ## Rule of thumb If you find yourself adding `evaluationDependsOn`, ask first whether a `Provider` removes the need. Reserve `evaluationDependsOn` for the rare case where you must inspect another project's *configured model* (not just an output) during configuration.
- How does a Provider sourced from a task output also handle task dependencies?When you pass tasks.named("jar").flatMap { it.archiveFile } into another task's input, Gradle infers an implicit dependency on the jar task, so it runs first automatically — no manual dependsOn.
- Give an eager API and its lazy replacement.tasks.getByName eager -> tasks.named lazy; tasks.create -> tasks.register; reading project(':lib').version -> a provider { } or wiring the producing task's output provider.
Eager reading is like grabbing a colleague's report before they've finished writing it — you get a half-done draft. A Provider is a promise to fetch the final report the moment you actually need it.
saying these in an interview costs you the question
- Saying provider.get() in a build script body is fine (that resolves eagerly).
- Claiming configuration cache is unrelated to cross-project reads.
- Defaulting to evaluationDependsOn instead of providers.