skip to content

What pitfalls arise when wiring ProviderFactory values into task inputs, and how do you keep them lazy and config-cache-safe?

level: seniorimportance: should knowfreq 30%

answer

  1. no .get() at configuration time
  2. Property.set(provider) keeps it lazy
  3. capture Provider not Project in doLast
  4. annotate @Input or it's ignored
  5. String providers -> map to type

basics

~10 s

Don't call .get() at configuration time, don't capture Project in task actions, and wire Providers into Property inputs with .set(provider) so resolution stays deferred and tracked.

solid answer

~40 s

The main pitfalls: (1) calling `.get()` on a ProviderFactory value during configuration forces an eager read and can break laziness/config-cache assumptions; wire providers into task `Property` inputs with `prop.set(provider)` and let Gradle resolve at execution. (2) Capturing `Project` (or `providers` obtained from project) inside a `doLast`/task action — instead capture the `Provider` itself into a local val. (3) Forgetting to mark the input (`@get:Input`/`@get:InputFiles`) so up-to-date checks miss it. (4) Expecting `providers.exec`/`ValueSource` to memoize across builds — they may re-run. (5) Type errors: `gradleProperty`/`environmentVariable` are `Provider<String>`, so convert with `.map`. Done right, values flow as providers end-to-end, are tracked as configuration-cache inputs, and resolve lazily at execution.

code

kotlin · 7 lines
kotlin
abstract class PublishTask : DefaultTask() {
    @get:Input abstract val token: Property<String>
}

tasks.register<PublishTask>("publish") {
    token.set(providers.environmentVariable("TOKEN")) // deferred + tracked
}

go deeper

for a junior

Know that you shouldn't call .get() early and should set providers into properties.

for a middle

Explain Property.set(provider), capturing the Provider in actions, and String->type via map.

for a senior

Cover input annotations, config-cache capture rules, and re-run vs memoization expectations.

for a principal

Codify these as review rules/lints so plugin authors keep the whole input graph lazy and tracked.

## The golden rule: keep it a Provider end-to-end ProviderFactory values are powerful only while they stay *unresolved*. The moment you call `.get()` at configuration time you (a) force the external read early and (b) often discard the laziness/tracking that justified using the API. ### Pitfall 1 — premature .get() ```kotlin // BAD: eager read at configuration time val token = providers.environmentVariable("TOKEN").get() // GOOD: wire the Provider into a Property input tasks.register<PublishTask>("publish") { this.token.set(providers.environmentVariable("TOKEN")) } ``` `Property.set(Provider)` keeps resolution deferred to execution and tracked. ### Pitfall 2 — capturing Project in a task action ```kotlin tasks.register("info") { // BAD: project captured into doLast -> config-cache failure doLast { println(project.providers.environmentVariable("X").get()) } } tasks.register("info2") { val x = providers.environmentVariable("X") // capture the Provider doLast { println(x.get()) } // OK at execution } ``` ### Pitfall 3 — untracked inputs A Provider feeding a task only participates in up-to-date checks / caching if it is a properly annotated input (`@get:Input`, `@get:InputFiles`, etc.). An un-annotated field is ignored by incremental checks. ### Pitfall 4 — assuming memoization `providers.exec` and `ValueSource` run when queried; with the configuration cache the value may be reused, but absent that they can re-run each build. Don't rely on a single execution for expensive work — cache deliberately. ### Pitfall 5 — string typing `environmentVariable`, `systemProperty`, `gradleProperty` all return `Provider<String>`. Convert with `.map { it.toInt() }` / `.map { it.toBoolean() }`; don't cast. ## Summary checklist - Wire with `prop.set(provider)`, not `prop.set(provider.get())`. - Capture the `Provider`, never `Project`, into task actions. - Annotate inputs so they affect up-to-date/caching. - Treat external reads as possibly-per-build; cache if costly. - Convert `String` providers via `map`.

  • Why is prop.set(provider) better than prop.set(provider.get())?
    set(provider) keeps resolution lazy and tracked until execution; set(provider.get()) forces an eager read at configuration time, losing laziness and risking staleness.
  • How do you safely use a ProviderFactory value inside a doLast block?
    Capture the Provider into a local val before the lambda and call .get() inside the action; never reference project/providers from within the action.
  • Why might a Provider wired to a task not trigger re-runs when its value changes?
    If the field isn't annotated as a task input (@get:Input/@get:InputFiles), Gradle's up-to-date/caching checks ignore it.

saying these in an interview costs you the question

  • Calling provider.get() eagerly during configuration
  • Referencing project inside doLast to read providers
  • Assuming an un-annotated Provider field affects up-to-date checks

context