skip to content

Lazy Configuration API

Gradle's lazy value model: Provider and Property, transformation chains, lazy collections, and the ObjectFactory that creates managed types. Interviewers ask because the entire modern authoring API is built on it.

on this pageshow

questions

page 1 of 2

What are ListProperty, SetProperty and MapProperty in Gradle, and how do you create them on a task or extension?

level: juniorimportance: must knowfreq 60%

answer

  1. ListProperty / SetProperty / MapProperty
  2. objects.listProperty(...) or abstract getter
  3. implement Provider of the collection
  4. lazy, deferred, accumulating
  5. default = empty, not undefined

basics

~10 s

They are lazy, collection-valued properties: ListProperty<T>, SetProperty<T>, MapProperty<K,V>. You create them via ObjectFactory (objects.listProperty(String::class.java)) and usually expose them as abstract getters on a task or extension.

solid answer

~30 s

ListProperty<T>, SetProperty<T> and MapProperty<K,V> are the collection-valued members of Gradle's lazy configuration API. Like Property<T> they hold a value computed lazily (at execution time, not configuration time), but the value is a List, Set or Map. You obtain them from ObjectFactory — objects.listProperty(String::class.java), objects.setProperty(...), objects.mapProperty(String::class.java, String::class.java) — or, more idiomatically, declare an abstract getter (val items: ListProperty<String>) and let Gradle's managed-property mechanism instantiate it for you. They give you deferred wiring (you can point them at a Provider before its value exists), incremental accumulation via add/addAll/put, and proper up-to-date checking when used as @Input on a task.

code

kotlin · 12 lines
kotlin
abstract class ReportTask : DefaultTask() {
    @get:Input abstract val authors: ListProperty<String>
    @get:Input abstract val labels: SetProperty<String>
    @get:Input abstract val props: MapProperty<String, String>
}

tasks.register<ReportTask>("report") {
    authors.add("alice")
    authors.addAll("bob", "carol")
    labels.add("release")
    props.put("version", "1.0")
}

go deeper

for a junior

Name the three types, that they are lazy and collection-valued, and that you get them from objects/ObjectFactory or an abstract getter.

for a middle

Explain the Provider relationship, the empty default, and why a plain List can't replace them (no deferred wiring, no accumulation, no proper input tracking).

for a senior

Discuss managed vs ObjectFactory construction, @Input snapshotting of collections, and finalization semantics.

for a principal

Frame them as part of the lazy-config contract that makes configuration-cache and parallel configuration safe across a plugin ecosystem.

## The problem they solve Gradle builds are configured eagerly but should compute values lazily — a task input might depend on another task's output that doesn't exist until execution. For single values, `Property<T>` defers the value behind a `Provider<T>`. For *collections*, Gradle provides three specialised properties: - **`ListProperty<T>`** — an ordered, duplicate-allowing `List<T>`. - **`SetProperty<T>`** — an unordered, de-duplicated `Set<T>`. - **`MapProperty<K, V>`** — a `Map<K, V>`. All three implement `Provider` of their collection type, so `get()`/`getOrElse(...)`/`map(...)` work, and the value is only materialised when queried. ## Creating them Two idiomatic ways: 1. **Via `ObjectFactory`** (the `objects` service) — explicit construction, useful inside an `init` block or a non-managed class. 2. **Managed properties** — declare an `abstract` getter returning the property type and Gradle generates the implementation. This is the preferred style for tasks and extensions. ```kotlin abstract class GenerateTask : DefaultTask() { @get:Input abstract val tags: ListProperty<String> @get:Input abstract val flags: SetProperty<String> @get:Input abstract val env: MapProperty<String, String> } ``` For a non-abstract class you inject `ObjectFactory`: ```kotlin val tags = objects.listProperty(String::class.java) val env = objects.mapProperty(String::class.java, String::class.java) ``` ## Why not a plain `List`? A plain `var tags: List<String>` is evaluated when assigned and cannot be wired to a `Provider`, cannot accumulate lazily, and does not participate in Gradle's input snapshotting the way a lazy property does. Collection properties give deferred resolution, lazy accumulation (`add`/`addAll`/`put`), and clean up-to-date checks. ## Default state A freshly created collection property is **not** undefined — it defaults to an *empty* collection (Gradle calls this the implicit empty value), so `get()` returns `[]`/`{}` rather than throwing. You can still override the starting point with `convention(...)` or reset with `empty()`.

  • What does ListProperty<T> return from get() if you never set a value?
    An empty list. Collection properties default to an empty collection, not to a missing/undefined value, so get() does not throw NoSuchElementException.
  • Why prefer an abstract getter over objects.listProperty(...)?
    Managed (abstract) properties let Gradle instantiate and finalize them, reduce boilerplate, and are the documented idiom for tasks/extensions. You only fall back to ObjectFactory when the class can't be managed.

saying these in an interview costs you the question

  • Saying they wrap an eager List that is evaluated at configuration time.
  • Claiming get() throws if unset — collection properties default to empty.
  • Confusing them with ConfigurableFileCollection (a different, file-specific type).

context

open as a page

What is a ConfigurableFileCollection in Gradle, and how do you create one in a plugin or task?

level: juniorimportance: must knowfreq 55%

basics

~10 s

A ConfigurableFileCollection is a mutable, lazily-evaluated set of files. You create one with project.objects.fileCollection() and populate it with from(...), so the actual files are resolved later, not at configuration time.

open as a page

What is ProjectLayout in Gradle, and how do you use it to point at a file or directory under the build directory?

level: juniorimportance: must knowfreq 55%

basics

~10 s

ProjectLayout (project.layout) gives lazy access to the project and build directories. You use layout.buildDirectory.file("name") or .dir("name") to point at outputs under build/ without resolving paths eagerly.

open as a page

What does Provider.map do in Gradle, and why is it preferred over reading a provider's value at configuration time?

level: juniorimportance: must knowfreq 62%

basics

~10 s

map transforms a provider's value into a new provider using a function, without computing it now. The transform runs lazily only when the value is finally queried, so it stays deferred until execution time.

open as a page

What is the ObjectFactory service in Gradle, and how do you obtain an instance of it?

level: juniorimportance: must knowfreq 55%

basics

~10 s

ObjectFactory is a Gradle service that creates instances of model objects like Property, FileCollection, and custom managed types. You get it via @Inject in a constructor or through project.objects.

open as a page

What is ProviderFactory in Gradle, and how do you obtain it inside a plugin or task?

level: juniorimportance: must knowfreq 55%

basics

~10 s

ProviderFactory creates lazy Provider values from sources like environment variables, system properties and Gradle properties. You get it via project.providers, or by @Inject in a task/plugin.

open as a page

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

level: juniorimportance: must knowfreq 70%

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.

open as a page

How do add, addAll, put and putAll accumulate values lazily on collection properties, and why does that matter?

level: middleimportance: must knowfreq 55%

basics

~10 s

add/addAll (list/set) and put/putAll (map) append to the property without resolving it. They accept plain values or Providers, and the actual elements are only computed when the property's value is queried.

open as a page

What is the difference between from() and setFrom() on a ConfigurableFileCollection?

level: middleimportance: must knowfreq 50%

basics

~10 s

from(...) appends new sources to whatever is already there; setFrom(...) replaces all existing sources with exactly what you pass. Use from to accumulate, setFrom to reset.

open as a page

How do you expose a ConfigurableFileCollection as a managed @InputFiles property on a custom task instead of an eager FileCollection?

level: middleimportance: must knowfreq 48%

basics

~10 s

Declare an abstract getter returning ConfigurableFileCollection, annotated with @InputFiles. Gradle manages the instance for you, so you never create or assign it — callers just use from() to set sources.

open as a page

How do you declare a task output file using RegularFileProperty, and what does Gradle do with it?

level: middleimportance: must knowfreq 50%

basics

~10 s

Declare an abstract val of type RegularFileProperty annotated @get:OutputFile. Set it to layout.buildDirectory.file(...). Gradle tracks that file for up-to-date checks and the build cache, and creates parent dirs.

open as a page

Explain the difference between Provider.map and Provider.flatMap, and when you must use flatMap.

level: middleimportance: must knowfreq 58%

basics

~10 s

map's function returns a plain value (T -> R, giving Provider<R>). flatMap's function returns another provider (T -> Provider<R>, also giving Provider<R> by flattening). Use flatMap when your transform itself produces a provider.

open as a page

What is a 'managed type' in Gradle, and how do abstract getters become managed properties?

level: middleimportance: must knowfreq 50%

basics

~10 s

A managed type is a class (usually abstract) where Gradle generates the implementation. Abstract getters returning Property/FileCollection/etc. become managed properties Gradle initializes automatically, so you don't write boilerplate or constructors.

open as a page

Why should you read environment variables through providers.environmentVariable instead of System.getenv() in a Gradle build?

level: middleimportance: must knowfreq 60%

basics

~10 s

providers.environmentVariable returns a lazy, config-cache-tracked Provider. System.getenv() is read eagerly at configuration time and invisible to the configuration cache, so the build can go stale.

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 is `new File(project.buildDir, "out.txt")` (or project.buildDir) discouraged, and what breaks with it under lazy configuration and the configuration cache?

level: seniorimportance: must knowfreq 42%

basics

~10 s

project.buildDir resolves the path eagerly at configuration time, isn't a Provider, and references the Project at execution. That breaks build-dir relocation, up-to-date tracking, and the configuration cache. Use layout.buildDirectory.file()/.dir() instead.

open as a page

A plugin author chains providers but the build still evaluates values too early or loses task ordering. What anti-patterns break provider laziness, and how do you keep the whole chain deferred?

level: seniorimportance: must knowfreq 38%

basics

~10 s

Calling get/getOrElse/getOrNull during configuration, capturing values into local variables, or unwrapping providers inside map lambdas forces eager evaluation and drops dependencies. Stay lazy with map/flatMap/zip/orElse and wire providers directly into task inputs.

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

Explain the difference between convention(), set()/value(), and empty() on a collection property, especially how they interact with add/addAll.

level: middleimportance: should knowfreq 40%

basics

~20 s

convention() sets a fallback used only until something is explicitly set. set()/value() replace the whole collection. empty() explicitly resets the property to an empty collection. add/addAll then append on top of whichever base is active.

open as a page

How does DirectoryProperty let you navigate to nested files and directories lazily, and where would you use it?

level: middleimportance: should knowfreq 38%

basics

~10 s

A DirectoryProperty holds a directory location lazily. From it you call .file("child.txt") or .dir("sub") to get a lazy Provider for nested paths. Use it for @OutputDirectory or a configurable base directory in an extension.

open as a page

What does Provider.orElse do, and how does it differ from convention() and from getOrElse()?

level: middleimportance: should knowfreq 40%

basics

~20 s

orElse returns a new provider that uses a fallback value or provider when the source is absent, staying lazy. convention sets a default on a Property that an explicit set() overrides. getOrElse eagerly returns a plain value or a default now.

open as a page

How do you combine two providers into one in Gradle while keeping the result lazy, and what happens if one input is absent?

level: middleimportance: should knowfreq 42%

basics

~10 s

Use providerA.zip(providerB) { a, b -> ... }. It builds a new lazy provider combining both values. If either input is absent, the combiner is skipped and the result is absent.

open as a page

What are fileProperty() and directoryProperty() from ObjectFactory, and why use them instead of java.io.File?

level: middleimportance: should knowfreq 42%

basics

~10 s

objects.fileProperty() returns a RegularFileProperty and objects.directoryProperty() a DirectoryProperty — lazy, location-aware wrappers around a file/dir. They defer resolution and track task dependencies, unlike a raw File.

open as a page

What is the difference between providers.gradleProperty(name) and project.findProperty(name) / project.property(name)?

level: middleimportance: should knowfreq 40%

basics

~10 s

gradleProperty returns a lazy, config-cache-tracked Provider<String> from gradle.properties and -P flags. findProperty/property read eagerly and also see project ext/extra properties, not just Gradle properties.

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

How do collection properties behave as task @Input snapshots and around finalization, and what mistakes break up-to-date / configuration-cache correctness?

level: seniorimportance: should knowfreq 30%

basics

~10 s

As @Input, a collection property's resolved value is snapshotted for up-to-date checks. Resolution is deferred until just before execution and then finalized, so changes (and contributed providers) are captured. Mutating after finalization throws.

open as a page

How does a ConfigurableFileCollection carry task dependencies, and when do you need builtBy()?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A ConfigurableFileCollection tracks the tasks that produce its files. If you add a task output or a Provider that knows its producer, dependencies are inferred automatically. builtBy() is for the case where you add raw files whose producing task Gradle can't infer.

open as a page

What are the lazy .elements and .asFileTree views of a ConfigurableFileCollection, and when do you use each?

level: seniorimportance: should knowfreq 30%

basics

~10 s

.elements returns a Provider<Set<FileSystemLocation>> — a lazy view you can map/wire without resolving now. .asFileTree turns the collection into a FileTree so you can walk directory contents recursively and apply include/exclude patterns.

open as a page

How do you derive one task's input file from another task's RegularFileProperty output so Gradle infers the task dependency automatically?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Set the consumer's input property to the producer's output Provider: consumer.input.set(producer.flatMap { it.outputFile }). Because the value is a Provider carrying task info, Gradle wires the dependency automatically — no dependsOn needed.

open as a page

showing 1–30 of 38