skip to content

providers.gradleProperty API

Reading Gradle, system, and environment values lazily through providers instead of project.property. Asked because eager reads at configuration time are exactly what the configuration cache forbids.

on this pageshow

questions

5

How do you read a value from gradle.properties lazily in a modern Gradle build, and why prefer providers.gradleProperty('x') over project.property('x')?

level: juniorimportance: must knowfreq 60%

answer

  1. Provider<String>, lazy box
  2. no Project reference captured
  3. config-cache-safe alternative to project.property
  4. gradleProperty vs systemProperty vs environmentVariable
  5. wire, don't .get() at config time

basics

~10 s

Use providers.gradleProperty("x"), which returns a Provider<String> read lazily at execution time. project.property("x") reads eagerly at configuration time and touches the Project object, which the configuration cache disallows.

solid answer

~40 s

`providers.gradleProperty("x")` returns a `Provider<String>` that is **lazy** — the value is read when the provider is queried (`.get()`), not when the line runs. You wire that provider into a task input or another property and the actual lookup happens later. By contrast `project.property("x")` (and `project.findProperty`) is **eager**: it returns the resolved value immediately and requires a live `Project` reference. The configuration cache serializes the task graph and forbids holding a `Project` at execution time, so `project.property` breaks it; the provider, holding only the property name, survives. `gradleProperty` resolves only against project properties (gradle.properties, `-P`, `ORG_GRADLE_PROJECT_*`) — it does NOT read system properties or env vars, for which you use `providers.systemProperty` / `providers.environmentVariable`.

code

kotlin · 10 lines
kotlin
// Lazy, config-cache-safe
val flag: Provider<String> = providers.gradleProperty("myFlag").orElse("default")

tasks.register<JavaExec>("run") {
    // wired lazily; read at execution time
    args(flag.get()) // .get() inside execution-time closures is fine
}

// Eager, breaks the configuration cache:
// val v = project.property("myFlag")

go deeper

for a junior

Know that gradleProperty returns a lazy Provider<String> and is the config-cache-safe replacement for project.property.

for a middle

Explain why project.property breaks the configuration cache (captures Project / eager read) and how wiring the provider into a task input fixes it.

for a senior

Discuss composing providers (orElse/map), the three providers.* sources, and avoiding premature .get() at configuration time.

for a principal

Frame a team convention: ban project.property in build logic, standardize provider wiring, and explain the impact on incremental + cached builds across many modules.

## The problem: eager vs lazy property reads Classic Gradle code reads a property like this: ```kotlin val v = project.property("myFlag") // eager, needs Project, runs at configuration time ``` This resolves *immediately* when the line executes (configuration time) and needs a live `Project` object. Two things make that bad in modern Gradle: 1. **Configuration cache**: Gradle serializes the configured task graph so subsequent builds skip configuration entirely. A serialized task must not hold a reference to `Project`, `Gradle`, `Settings`, etc. `project.property` forces exactly such a reference, so the cache reports it as a problem. 2. **Lazy configuration / wiring**: you often want to *describe* where a value comes from and defer reading it until it is actually needed (task execution), so values set later still take effect. ## The provider API `providers` is a `ProviderFactory`, available on `Project` (and injectable). Its property-reading methods each return a `Provider<String>`: - `providers.gradleProperty("x")` — a **project (Gradle) property**: from `gradle.properties`, `-Px=...`, or `ORG_GRADLE_PROJECT_x` env var. Returns empty (absent) if undefined. - `providers.systemProperty("x")` — a **JVM system property** (`-Dx=...`). - `providers.environmentVariable("X")` — an **environment variable**. A `Provider<T>` is a lazy box: it knows *how* to produce a value but only does so when queried via `.get()`, `.getOrNull()`, `.getOrElse(default)`, or `.orElse(...)`. Wiring it into a task input means Gradle reads it at execution time and tracks it as a build-cache/config-cache input automatically. ## Why this is configuration-cache-safe The provider captures only the *name* of the property and a reference to the provider factory machinery — not a `Project`. When Gradle serializes the task graph it can store "read gradle property x" as an input. On a cache hit it can detect that the property's value changed and invalidate correctly. No `Project` is captured, so no config-cache violation. ## Wiring, not getting The idiomatic move is to **wire** the provider, never `.get()` it at configuration time: ```kotlin tasks.register<MyTask>("run") { flag.set(providers.gradleProperty("myFlag").orElse("default")) } ``` Here `flag` is a `Property<String>` task input; the provider chain (`gradleProperty(...).orElse(...)`) is evaluated lazily and recorded as an input. Calling `.get()` in the configuration body would re-introduce eager reads and lose the laziness benefit. ## Defaults and transforms Providers compose: `.orElse("x")` supplies a fallback, `.map { it.toInt() }` transforms the value lazily, `.isPresent` checks existence without forcing a value at config time (when wired). This lets you express a whole pipeline that only runs on demand.

  • What does providers.gradleProperty return when the property is undefined?
    A provider with no value (absent / not present). Querying .get() throws; use .getOrNull(), .getOrElse(default), or .orElse(...) to handle the missing case.
  • Does gradleProperty read system properties set with -D?
    No. gradleProperty reads only project (Gradle) properties. For -D system properties use providers.systemProperty; for environment variables use providers.environmentVariable.

project.property is calling the warehouse now and reading you the number off the shelf; providers.gradleProperty hands you a slip that says 'go ask shelf x when you actually need it' — so the lookup happens later and you never carry the whole warehouse around.

saying these in an interview costs you the question

  • Saying providers.gradleProperty reads -D system properties or env vars — it reads only project properties.
  • Claiming it returns the value directly — it returns a Provider<String>, not a String.

context

open as a page

Distinguish providers.gradleProperty, providers.systemProperty, and providers.environmentVariable. When would you reach for each?

level: middleimportance: must knowfreq 50%

basics

~10 s

gradleProperty reads project properties (gradle.properties, -P, ORG_GRADLE_PROJECT_). systemProperty reads JVM system properties (-D). environmentVariable reads OS env vars. All three return a lazy Provider<String>.

open as a page

Using providers.gradleProperty, how do you supply a default and handle a missing property without breaking the configuration cache?

level: middleimportance: should knowfreq 40%

basics

~10 s

Use .orElse("default") or .getOrElse("default") on the provider, or .getOrNull() to detect absence. Avoid calling .get() at configuration time when the property might be missing.

open as a page

You're migrating a build to the configuration cache and the report flags many 'reading project property' violations. How do you fix property reads using the providers API, and what subtle behavior changes should you watch for?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Replace project.property/findProperty and System.getProperty/getenv reads with providers.gradleProperty/systemProperty/environmentVariable, wired lazily into task inputs. Watch that absence now stays lazy and that values are read at execution, not configuration, time.

open as a page

Show how to wire providers.gradleProperty into a custom task input the lazy way, and explain what goes wrong if you call .get() in the configuration block.

level: seniorimportance: should knowfreq 35%

basics

~10 s

Declare a Property<String> task input and set it to the provider: input.set(providers.gradleProperty("x").orElse("d")). Calling .get() in the configuration block resolves eagerly, loses laziness, and can break config-cache or up-to-date checks.

open as a page