skip to content

Your plugin defines an extension with a `Property<String>` called `greeting`. How do you connect that extension value to a task's input property so the task uses whatever the user sets in the build script?

level: juniorimportance: must knowfreq 60%

answer

  1. set(provider) not set(value)
  2. extension applied before user DSL block
  3. Property is a Provider
  4. never .get() at configuration time
  5. value chain stays live

basics

~10 s

Use task.greeting.set(extension.greeting). This wires the task's Property to the extension's Property lazily, so the value is read later (at execution), not while configuring.

solid answer

~40 s

Both the extension and the task expose a `Property<String>`. In the plugin's `apply` method you register the task and call `task.configure { greeting.set(extension.greeting) }`. Passing the *provider* (the extension's `Property` is itself a `Provider`) instead of `extension.greeting.get()` keeps the wiring lazy: Gradle defers resolving the value until the task actually runs (or until `get()` is called). This matters because at configuration time the user may not have set the extension value yet — the build script block that configures the extension can run after your plugin's `apply`. Calling `.get()` during configuration captures whatever value exists at that instant (often the default or empty), so the user's later assignment is lost. Wiring provider-to-property preserves the value chain end to end.

code

kotlin · 17 lines
kotlin
abstract class GreetExtension {
    abstract val greeting: Property<String>
}

abstract class GreetTask : DefaultTask() {
    @get:Input abstract val greeting: Property<String>
    @TaskAction fun run() = println(greeting.get())
}

class GreetPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val ext = project.extensions.create("greet", GreetExtension::class.java)
        project.tasks.register("greet", GreetTask::class.java) {
            it.greeting.set(ext.greeting) // lazy wiring, no .get()
        }
    }
}

go deeper

for a junior

Know the literal call task.prop.set(extension.prop) and that you must not call .get() while configuring.

for a middle

Explain the apply-vs-user-DSL ordering and why the provider must stay unresolved.

for a senior

Discuss where the wiring lives, that register keeps it lazy, and how the value chain resolves at execution.

for a principal

Frame provider wiring as the contract that makes plugins configuration-cache and ordering safe across a large build.

## The problem this solves A Gradle plugin typically does two things in `apply(project)`: it creates an **extension** (the DSL block users write into, e.g. `myPlugin { greeting = "hi" }`) and it **registers tasks** that do the work. The plugin's `apply` runs *early*, during the project's configuration. But the user's `myPlugin { ... }` block runs *after* `apply`. So at the moment your plugin code runs, the extension still holds its default value. If you read the extension eagerly — `task.greeting = extension.greeting.get()` — you snapshot the default, and the user's later assignment never reaches the task. ## Lazy wiring with Provider/Property Gradle's solution is the **lazy configuration API**: `Provider<T>` (a deferred value you can read later) and `Property<T>` (a `Provider` you can also write to). The key move is to **connect the task's `Property` to the extension's `Property`** rather than copying a resolved value: ```kotlin task.greeting.set(extension.greeting) ``` Here `extension.greeting` is a `Property<String>`, which *is a* `Provider<String>`. `set(Provider)` establishes a live link: when something later calls `task.greeting.get()` (Gradle does this at execution, when it reads the task's `@Input`), it walks the chain back to the extension and reads *its current* value — which by then includes the user's assignment. ## Why not `.get()` during configuration `get()` forces the value *now*. During `apply`, "now" is before the user DSL runs. The fix is never to call `get()` in plugin/configuration code; pass the provider through untouched. ## Where the wiring lives The wiring belongs in the plugin, inside the task-registration callback, so it's set up exactly once per task: ```kotlin val ext = project.extensions.create("myPlugin", MyExtension::class.java) project.tasks.register("greet", GreetTask::class.java) { it.greeting.set(ext.greeting) } ``` Because `register` is itself lazy, the configuration block only runs if the task is actually needed — and even then the value resolution is deferred further to execution.

  • Why does passing `extension.greeting` work where `extension.greeting.get()` would fail?
    `extension.greeting` is a `Provider`, so `set` keeps a live link resolved later; `.get()` resolves immediately during configuration, before the user's DSL block has set the value.
  • Where in the plugin should this wiring go?
    Inside the `tasks.register(...)` configuration callback, so it's established once per task and stays lazy.

Wiring providers is like subscribing to a magazine versus photocopying one issue: set(provider) keeps you subscribed (you get whatever the final value is), while .get() photocopies whatever happens to be on the stand right now.

saying these in an interview costs you the question

  • Saying you should call `.get()` and assign the plain value during `apply`.
  • Believing the user's DSL block runs before the plugin's `apply` method.

context