Why is providers.gradleProperty() preferred over project.findProperty() when reading a project property in a modern Gradle build?
answer
- Provider = lazy, declared input
- findProperty = eager + reads project
- configuration cache safety
- wire Provider into task Property
- .map / .orElse composition
basics
~10 sproviders.gradleProperty() returns a lazy Provider that is configuration-cache compatible and tracked as a build input. project.findProperty() reads eagerly at configuration time and touches the Project object, which breaks the configuration cache and isn't lazy.
solid answer
~50 s`project.findProperty("x")` resolves the value **eagerly during configuration** and reads the `Project` instance directly. Under the **configuration cache**, capturing `project` (or reading a property eagerly when you should be lazy) is exactly the kind of access that invalidates or forbids caching. `providers.gradleProperty("x")` instead returns a `Provider<String>` — a lazy handle that is only resolved when something `.get()`s it, typically at execution time. Gradle records that provider as a **declared build input**, so changing the property value correctly invalidates the configuration cache and the value is wired without ever holding a reference to `project`. You can chain `.map { ... }`, `.orElse(default)`, and feed it straight into a task `Property`. The practical rule: read gradle properties through `providers.gradleProperty(...)` and pipe the Provider into your task inputs; reserve `findProperty` for quick scripts where the configuration cache is off.
code
kotlin · 7 linesval release = providers.gradleProperty("release")
.map { it.toBoolean() }
.orElse(false)
tasks.named<Jar>("jar") {
archiveClassifier.set(release.map { if (it) "" else "snapshot" })
}go deeper
Know both exist and that providers.gradleProperty is the modern lazy reader.
Explain laziness, the Provider type, and wiring it into a task Property with orElse defaults.
Tie it to configuration-cache correctness and declared inputs; avoid capturing project in tasks.
Drive a convention of provider-based property reads across the codebase to keep the configuration cache reliably enabled at scale.
## The two APIs ```kotlin // Legacy, eager val a = project.findProperty("appVersion") as String? // String? right now // Modern, lazy val b = providers.gradleProperty("appVersion") // Provider<String> ``` `findProperty` returns the value *immediately* (or `null`). `gradleProperty` returns a **`Provider`** — a lazily-evaluated box whose contents are computed only when `.get()`/`.getOrElse()` is called. ## Why lazy + provider-based wins ### Configuration cache The **configuration cache** stores the result of the configuration phase so later builds skip it. To do that safely, tasks must not capture live references to `Project`, `Gradle`, etc., and inputs must be *declared* rather than read on the fly. `project.findProperty` reads `project` at configuration time — disallowed/penalized under the CC. `providers.gradleProperty` is a first-class, CC-serializable input: Gradle knows the property name, fingerprints its value, and invalidates the cache when it changes. ### Laziness & wiring A `Provider` can be wired directly into a task's lazy `Property`: ```kotlin tasks.register<WriteVersion>("writeVersion") { version.set(providers.gradleProperty("appVersion").orElse("0.0.1")) } ``` Nothing is resolved until the task actually runs — so an unrelated build (e.g. `./gradlew help`) never pays the cost, and an absent property doesn't blow up configuration. ### Transformation Providers compose: `.map { it.toInt() }`, `.orElse(...)`, `.zip(other) { ... }`. You build a small dataflow graph instead of imperative reads. ## When findProperty is still okay Throwaway scripts, or code where the configuration cache is not enabled and you genuinely need an eager nullable value for a branch. Even then, prefer the provider and call `.getOrNull()` if you must branch. ## Gotcha `providers.gradleProperty` only sees **project properties** (file / `-P`). For `-D` system properties use `providers.systemProperty(...)`; for environment variables `providers.environmentVariable(...)`.
- What does providers.gradleProperty return when the property is absent and you call .get()?It throws a MissingValueException because the Provider has no value. Use .getOrElse(default), .orElse(default), or .getOrNull() to handle absence.
- Does providers.gradleProperty also see -D system properties?No. It only reads project properties (gradle.properties / -P). For system properties use providers.systemProperty(), and providers.environmentVariable() for env vars.
findProperty is reading a gauge right now; gradleProperty is wiring a sensor whose reading is only sampled when the machine actually runs — and the wiring itself is recorded so the controller knows when to recalibrate.
saying these in an interview costs you the question
- Claiming findProperty and gradleProperty are equivalent — one is eager and CC-hostile, the other lazy and CC-friendly.
- Calling .get() on a possibly-absent provider at configuration time and assuming it returns null instead of throwing.
- Thinking gradleProperty reads -D system properties.