How do you reference and wire a task declared with tasks.register without realizing it? Show how to use the returned TaskProvider.
answer
- TaskProvider = typed lazy promise
- configure {} adds config lazily
- dependsOn(provider) defers resolution
- map / flatMap to read outputs
- .get() only at execution time
basics
~10 sregister returns a TaskProvider. Pass the provider itself to dependsOn or inputs — Gradle resolves it lazily. Use provider.configure { } to add config, and flatMap to read its outputs without calling get().
solid answer
~40 sThe `TaskProvider<T>` from `register` is a lazy handle you wire *as a provider*, never by resolving it early. Gradle's APIs accept providers directly: `dependsOn(myProvider)`, `from(zipProvider)`, `inputs.files(compileProvider)` — all defer resolution until the task graph is built. To add more configuration after registration, use `myProvider.configure { ... }` (still lazy) rather than `myProvider.get().something`. To consume a task's output, map through the provider: `zipTask.flatMap { it.archiveFile }` yields a `Provider<RegularFile>` that only realizes `zipTask` if its output is actually needed. The cardinal rule: keep everything in `Provider`/`TaskProvider` space; the moment you call `.get()` (or read a concrete property) you realize the task and break the laziness chain. In Kotlin DSL, `val z by registering(Zip::class) { }` gives you the same provider via delegation.
code
kotlin · 9 linesval compile = tasks.register("compileThing", JavaCompile::class)
// add config later, still lazy
compile.configure { options.encoding = "UTF-8" }
val jar = tasks.register("jarThing", Jar::class) {
dependsOn(compile) // provider accepted directly
from(compile.map { it.destinationDirectory }) // lazy output wiring
}go deeper
Know that register returns a TaskProvider and you can pass it to dependsOn instead of a task name.
Use configure{}, dependsOn(provider), and map/flatMap to wire tasks without calling get() during configuration.
Design plugin APIs that expose Provider-typed outputs so consumers wire lazily; explain map vs flatMap and execution-time get().
Set conventions for provider-based wiring across the org's shared plugins to guarantee configuration avoidance and configuration-cache compatibility.
## TaskProvider is a lazy reference `val compile = tasks.register("compile", JavaCompile::class)` returns a `TaskProvider<JavaCompile>`. Think of it as a typed promise. You can do three things lazily with it: 1. **Configure later:** `compile.configure { options.encoding = "UTF-8" }` — the action runs when (if) `compile` is realized. 2. **Depend on it:** `tasks.register("bundle") { dependsOn(compile) }` — Gradle accepts the provider and resolves the dependency when building the graph. 3. **Read its outputs lazily:** map through it. ## Lazy output wiring with map / flatMap The key trick for cross-task wiring is **provider chaining**: ```kotlin val zipIt = tasks.register("zipIt", Zip::class) { from("build/libs") archiveFileName.set("app.zip") } // archiveFile is itself a Provider<RegularFile>; flatMap composes them val zipFile: Provider<RegularFile> = zipIt.flatMap { it.archiveFile } tasks.register("publishZip", Copy::class) { from(zipFile) // lazy: realizes zipIt only if publishZip runs into("dist") } ``` - Use **`map`** when the lambda returns a plain value. - Use **`flatMap`** when the lambda returns another `Provider`/`Property` (e.g. `archiveFile`). Because you never call `.get()`, `zipIt` stays unrealized until `publishZip` is on the execution path. ## When you must resolve Calling `.get()` is legitimate at *execution time* (inside a `doLast`/task action) where everything is already realized. The anti-pattern is calling `.get()` during *configuration*. ## Kotlin DSL delegation ```kotlin val zipIt by registering(Zip::class) { archiveFileName.set("app.zip") } ``` `by registering` is sugar that both registers the task and binds the local `val` to the `TaskProvider`. ## Summary Wire providers to providers. Configure via `.configure {}`, depend via `dependsOn(provider)`, read outputs via `map`/`flatMap`. Reserve `.get()` for execution time. This preserves the configuration-avoidance benefit that `register` exists to provide.
- When is calling provider.get() acceptable?At execution time — inside a task action (doLast/doFirst) or after the graph is built — where the task is already realized. Avoid it during the configuration phase.
- What is the difference between map and flatMap on a provider?map transforms the value with a lambda returning a plain value; flatMap is for when the lambda returns another Provider/Property (it flattens the nested provider), e.g. mapping to archiveFile.
saying these in an interview costs you the question
- Calling provider.get() during configuration just to read a property.
- Storing the resolved Task instead of the TaskProvider and re-using it.
- Using map where flatMap is needed (yielding a Provider<Provider<...>>).