skip to content

Provider & Property Basics

Provider as a deferred read and Property as a deferred write, with get, getOrElse, and presence checks. Asked because calling get() too early is the single most common lazy-API mistake.

on this pageshow

questions

6

What is the difference between Provider<T> and Property<T> in Gradle's lazy configuration API?

level: juniorimportance: must knowfreq 70%

answer

  1. Provider = read-only lazy value
  2. Property extends Provider + set()/convention()
  3. get / getOrNull / getOrElse / isPresent
  4. create via objects.property(Type::class)
  5. resolved at execution, not configuration

basics

~10 s

Provider<T> is a read-only lazy value you query with get(). Property<T> extends Provider<T> and is also writable — you can set() its value or wire it to another provider.

solid answer

~40 s

`Provider<T>` represents a value that is **calculated lazily** — it is read-only and you obtain the value via `get()`, `getOrNull()`, or `getOrElse()`. `Property<T>` is a sub-interface of `Provider<T>` that adds a **mutable** side: `set(value)` and `set(provider)`, plus `convention(...)` for defaults. So every `Property` is a `Provider`, but not vice versa. In a task or extension you typically declare inputs as `Property<T>` so users can configure them, and expose derived/computed values as `Provider<T>`. The key win over a plain field is **deferred resolution**: the value isn't computed at configuration time but when something actually reads it — usually during task execution — which lets Gradle build the value lazily and avoid eager work it may not need.

code

kotlin · 6 lines
kotlin
val objects = project.objects
val message: Property<String> = objects.property(String::class)
message.set("hello")           // Property is writable
val shout: Provider<String> =  // Provider is read-only / derived
    message.map { it.uppercase() }
println(shout.get())            // resolved lazily -> HELLO

go deeper

for a junior

State that Provider is read-only (get) and Property adds set(); creating via project.objects.

for a middle

Explain the inheritance (Property extends Provider), convention(), and why you declare inputs as Property and expose derived values as Provider.

for a senior

Tie it to deferred resolution at execution time, automatic task input/output wiring, and configuration-cache compatibility.

for a principal

Frame lazy types as the org-wide standard for plugin authoring, enabling config-cache adoption and avoiding eager configuration-phase cost across a large build.

## The problem lazy types solve In old Gradle code you'd write a task with a plain field like `String outputName` and assign it during the configuration phase. That forces the value to be computed eagerly, even if the task never runs, and it can't react to values produced later by other tasks. The **lazy configuration API** replaces those fields with `Provider<T>` and `Property<T>` so values are computed **on demand**. ## Provider<T> A `Provider<T>` is a **read-only** container for a value that will be calculated lazily. You never store the raw value in it directly — you query it: - `get()` — returns the value, or throws `IllegalStateException` if no value is present. - `getOrNull()` — returns the value or `null` if absent. - `getOrElse(default)` — returns the value or the supplied default. - `isPresent` — `true` if a value can be computed. ## Property<T> `Property<T>` **extends** `Provider<T>` and adds mutation: - `set(T)` / `set(Provider<T>)` — assign a value or wire to another provider. - `convention(T)` / `convention(Provider<T>)` — a fallback used only if nothing is explicitly set. - `value(...)` — set and return the property (handy in a fluent style). Because `Property` *is* a `Provider`, you can pass it anywhere a `Provider` is expected. This is the everyday split: declare configurable **inputs** as `Property` on your extension/task, and expose computed/derived outputs as `Provider`. ## Creating them You don't `new` these. You ask Gradle's `ObjectFactory` (`project.objects`): ```kotlin val name: Property<String> = objects.property(String::class) val upper: Provider<String> = name.map { it.uppercase() } ``` ## Deferred resolution The value behind a `Property` set with `set(otherProvider)` is **not** evaluated until someone calls `get()` — typically during the execution phase. That means a property can depend on a value produced by another task and still resolve correctly, because resolution happens after configuration is done. ## Why it matters Lazy types give you: deferred computation (no wasted work), correct **task input/output wiring** (Gradle can track dependencies automatically when one task's output `Provider` feeds another's input `Property`), and better support for the **configuration cache**, which requires that values be captured as providers rather than eagerly resolved Java fields.

  • Can you call set() on a Provider<T>?
    No. Provider<T> is read-only — it has no set(). Only Property<T> (which extends Provider) is mutable. To derive a new value from a Provider you use transformations like map/flatMap, which return another Provider.
  • Why prefer Property over a plain Kotlin var on a task?
    A Property defers resolution to execution time, can be wired to other tasks' outputs so Gradle tracks dependencies automatically, supports conventions, and is compatible with the configuration cache — a plain field is eager and breaks that wiring.

A Provider is like a read-only sealed envelope you can only open (get) when you need it; a Property is the same envelope but you're also allowed to put new contents in (set).

saying these in an interview costs you the question

  • Saying Provider is writable — it has no set().
  • Claiming Property and Provider are unrelated types rather than Property extending Provider.
  • Thinking the value is computed when you call set() rather than when get() reads it.

context

open as a page

Explain the difference between get(), getOrNull(), and getOrElse() on a Provider, and when each is appropriate.

level: middleimportance: must knowfreq 55%

basics

~20 s

get() returns the value or throws if absent. getOrNull() returns the value or null. getOrElse(default) returns the value or the supplied default. Pick based on whether a missing value is an error or has a fallback.

open as a page

Why does Gradle resolve Provider/Property values lazily at execution time, and what problems does eager resolution cause?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Lazy resolution means a value is computed only when read (usually at execution), not when configured. This avoids wasted work for tasks that don't run, lets a value depend on results produced later, and keeps the build configuration-cache friendly.

open as a page

What does isPresent mean on a Property, and how does convention() interact with set() and presence?

level: middleimportance: should knowfreq 35%

basics

~10 s

isPresent is true when a value can be computed — from an explicit set() or a convention. convention() supplies a default used only if nothing is explicitly set; an explicit set() overrides the convention.

open as a page

How do you obtain Property and Provider instances in a plugin, and why can't you instantiate them directly?

level: middleimportance: should knowfreq 40%

basics

~10 s

They're interfaces with no public constructor. You create a Property via ObjectFactory (project.objects.property(Type::class)) and create read-only Providers via project.provider { ... } or by transforming an existing Provider.

open as a page

You're modernizing an old custom task that uses plain String/File fields. How do you migrate it to Property/Provider, and what pitfalls do you watch for?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Replace eager fields with Property<T> declared as abstract managed properties or created via ObjectFactory, annotate inputs/outputs, set defaults with convention(), and read values at execution (inside the action) instead of at configuration time.

open as a page