skip to content

Why is providers.gradleProperty() preferred over project.findProperty() when reading a project property in a modern Gradle build?

level: middleimportance: must knowfreq 55%

answer

  1. Provider = lazy, declared input
  2. findProperty = eager + reads project
  3. configuration cache safety
  4. wire Provider into task Property
  5. .map / .orElse composition

basics

~10 s

providers.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 lines
kotlin
val release = providers.gradleProperty("release")
    .map { it.toBoolean() }
    .orElse(false)

tasks.named<Jar>("jar") {
    archiveClassifier.set(release.map { if (it) "" else "snapshot" })
}

go deeper

for a junior

Know both exist and that providers.gradleProperty is the modern lazy reader.

for a middle

Explain laziness, the Provider type, and wiring it into a task Property with orElse defaults.

for a senior

Tie it to configuration-cache correctness and declared inputs; avoid capturing project in tasks.

for a principal

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.

context