As a build grows in complexity, knowing when and where a particular value is configured can become difficult to reason about. Gradle provides several ways to manage this complexity using lazy configuration.

Lazy properties

The Provider API is currently incubating. Please be aware that the DSL and other configuration may change in later Gradle versions.

Gradle provides lazy properties, which delay the calculation of a property’s value until it’s actually required. These provide three main benefits to build script and plugin authors:

  1. Build authors can wire together Gradle models without worrying when a particular property’s value will be known. For example, you may want to set the input source files of a task based on the source directories property of an extension but the extension property value isn’t known until the build script or some other plugin configures them.

  2. Build authors can wire an output property of a task into an input property of some other task and Gradle automatically determines the task dependencies based on this connection. Property instances carry information about which task, if any, produces their value. Build authors do not need to worry about keeping task dependencies in sync with configuration changes.

  3. Build authors can avoid resource intensive work during the configuration phase, which can have a large impact on build performance. For example, when a configuration value comes from parsing a file but is only used when functional tests are run, using a property instance to capture this means that the file is parsed only when the functional tests are run, but not when, for example, clean is run.

Gradle represents lazy properties with two interfaces:

  • Provider represents a value that can only be queried and cannot be changed.

    • Properties with these types are read-only.

    • The method Provider.get() returns the current value of the property.

    • A Provider can be created from another Provider using Provider.map(Transformer).

    • Many other types extend Provider and can be used where-ever a Provider is required.

  • Property represents a value that can be queried and also changed.

    • Properties with these types are configurable.

    • Property extends the Provider interface.

    • The method Property.set(T) specifies a value for the property, overwriting whatever value may have been present.

    • The method Property.set(Provider) specifies a Provider for the value for the property, overwriting whatever value may have been present. This allows you to wire together Provider and Property instances before the values are configured.

    • A Property can be created by the factory method ObjectFactory.property(Class).

Lazy properties are intended to be passed around and only queried when required. Usually, this will happen during the execution phase. For more information about the Gradle build phases, please see Build Lifecycle.

The following demonstrates a task with a configurable greeting property and a read-only message property that is derived from this:

Example 1. Using a read-only and configurable property
build.gradle
// A task that displays a greeting
class Greeting extends DefaultTask {
    // A configurable greeting
    @Input
    final Property<String> greeting = project.objects.property(String)

    // Read-only property calculated from the greeting
    @Internal
    final Provider<String> message = greeting.map { it + ' from Gradle' }

    @TaskAction
    void printMessage() {
        logger.quiet(message.get())
    }
}

task greeting(type: Greeting) {
    // Configure the greeting
    greeting.set('Hi')

    // Note that an assignment statement can be used instead of calling Property.set()
    greeting = 'Hi'
}
build.gradle.kts
// A task that displays a greeting
open class Greeting : DefaultTask() {
    // Configurable by the user
    @Input
    val greeting: Property<String> = project.objects.property()

    // Read-only property calculated from the greeting
    @Internal
    val message: Provider<String> = greeting.map { it + " from Gradle" }

    @TaskAction
    fun printMessage() {
        logger.quiet(message.get())
    }
}

task<Greeting>("greeting") {
    // Configure the greeting
    greeting.set("Hi")
}
Output of gradle greeting
> gradle greeting

> Task :greeting
Hi from Gradle

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

The Greeting task has a property of type Property<String> to represent the configurable greeting and a property of type Provider<String> to represent the calculated, read-only, message. The message Provider is created from the greeting Property using the map() method, and so its value is kept up-to-date as the value of the greeting property changes.

Note that Gradle Groovy DSL will generate setter methods for each Property-typed property in a task implementation. These setter methods allow you to configure the property using the assignment (=) operator as a convenience.

Kotlin DSL conveniences will be added in a future release.

Creating a Property or Provider instance

Neither Provider nor its subtypes such as Property are intended to be implemented by a build script or plugin author. Gradle provides factory methods to create instances of these types instead. See the Quick Reference for all of the types and factories available. In the previous example, we have seen 2 factory methods:

A Provider can also be created by the factory method ProviderFactory.provider(Callable). You should prefer using map() instead, as this has some useful benefits, which we will see later.

There are no specific methods create a provider using a groovy.lang.Closure. When writing a plugin or build script with Groovy, you can use the map(Transformer) method with a closure and Groovy will take care of converting the closure to a Transformer. You can see this in action in the previous example.

Similarly, when writing a plugin or build script with Kotlin, the Kotlin compiler will take care of converting a Kotlin function into a Transformer.

Connecting properties together

An important feature of lazy properties is that they can be connected together so that changes to one property are automatically reflected in other properties. Here’s an example where the property of a task is connected to a property of a project extension:

Example 2. Connecting properties together
build.gradle
// A project extension
class MessageExtension {
    // A configurable greeting
    final Property<String> greeting

    @javax.inject.Inject
    MessageExtension(ObjectFactory objects) {
        greeting = objects.property(String)
    }
}

// A task that displays a greeting
class Greeting extends DefaultTask {
    // A configurable greeting
    @Input
    final Property<String> greeting = project.objects.property(String)

    // Read-only property calculated from the greeting
    @Internal
    final Provider<String> message = greeting.map { it + ' from Gradle' }

    @TaskAction
    void printMessage() {
        logger.quiet(message.get())
    }
}

// Create the project extension
project.extensions.create('messages', MessageExtension, project.objects)

// Create the greeting task
task greeting(type: Greeting) {
    // Attach the greeting from the project extension
    // Note that the values of the project extension have not been configured yet
    greeting.set(project.messages.greeting)

    // Note that an assignment statement can be used instead of calling Property.set()
    greeting = project.messages.greeting
}

messages {
    // Configure the greeting on the extension
    // Note that there is no need to reconfigure the task's `greeting` property. This is automatically updated as the extension property changes
    greeting = 'Hi'
}
build.gradle.kts
// A project extension
open class MessageExtension(objects: ObjectFactory) {
    // A configurable greeting
    val greeting: Property<String> = objects.property()
}

// A task that displays a greeting
open class Greeting : DefaultTask() {
    // Configurable by the user
    @Input
    val greeting: Property<String> = project.objects.property()

    // Read-only property calculated from the greeting
    @Internal
    val message: Provider<String> = greeting.map { it + " from Gradle" }

    @TaskAction
    fun printMessage() {
        logger.quiet(message.get())
    }
}

// Create the project extension
val messages = project.extensions.create("messages", MessageExtension::class, project.objects)

// Create the greeting task
task<Greeting>("greeting") {
    // Attach the greeting from the project extension
    // Note that the values of the project extension have not been configured yet
    greeting.set(messages.greeting)
}

configure<MessageExtension> {
    // Configure the greeting on the extension
    // Note that there is no need to reconfigure the task's `greeting` property. This is automatically updated as the extension property changes
    greeting.set("Hi")
}
Output of gradle greeting
> gradle greeting

> Task :greeting
Hi from Gradle

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

This example calls the Property.set(Provider) method to attach a Provider to a Property to supply the value of the property. In this case, the Provider happens to be a Property as well, but you can connect any Provider implementation, for example one created using Provider.map()

Working with files

In Working with Files, we introduced four collection types for File-like objects:

Table 1. Collection of files recap
Read-only Type Configurable Type

FileCollection

ConfigurableFileCollection

FileTree

ConfigurableFileTree

All of these types are also considered lazy types.

In this section, we are going to introduce more strongly typed models types to represent elements of the file system: Directory and RegularFile. These types shouldn’t be confused with the standard Java File type as they are used to tell Gradle, and other people, that you expect more specific values such as a directory or a non-directory, regular file.

Gradle provides two specialized Property subtypes for dealing with values of these types: RegularFileProperty and DirectoryProperty. ObjectFactory has methods to create these: ObjectFactory.fileProperty() and ObjectFactory.directoryProperty().

A DirectoryProperty can also be used to create a lazily evaluated Provider for a Directory and RegularFile via DirectoryProperty.dir(String) and DirectoryProperty.file(String) respectively. These methods create providers whose values are calculated relative to the location for the DirectoryProperty they were created from. The values returned from these providers will reflect changes to the DirectoryProperty.

Example 3. Using file and directory property
build.gradle
// A task that generates a source file and writes the result to an output directory
class GenerateSource extends DefaultTask {
    // The configuration file to use to generate the source file
    @InputFile
    final RegularFileProperty configFile = project.objects.fileProperty()

    // The directory to write source files to
    @OutputDirectory
    final DirectoryProperty outputDir = project.objects.directoryProperty()

    @TaskAction
    def compile() {
        def inFile = configFile.get().asFile
        logger.quiet("configuration file = $inFile")
        def dir = outputDir.get().asFile
        logger.quiet("output dir = $dir")
        def className = inFile.text.trim()
        def srcFile = new File(dir, "${className}.java")
        srcFile.text = "public class ${className} { ... }"
    }
}

// Create the source generation task
task generate(type: GenerateSource) {
    // Configure the locations, relative to the project and build directories
    configFile = project.layout.projectDirectory.file('src/main/config.txt')
    outputDir = project.layout.buildDirectory.dir('generated-source')

    // Note that a `File` instance can be used as a convenience to set a location
    configFile = file('src/config.txt')
}

// Change the build directory
// Don't need to reconfigure the task properties. These are automatically updated as the build directory changes
buildDir = 'output'
build.gradle.kts
// A task that generates a source file and writes the result to an output directory
open class GenerateSource @javax.inject.Inject constructor(objects: ObjectFactory): DefaultTask() {
    @InputFile
    val configFile: RegularFileProperty = objects.fileProperty()

    @OutputDirectory
    val outputDir: DirectoryProperty = objects.directoryProperty()

    @TaskAction
    fun compile() {
        val inFile = configFile.get().asFile
        logger.quiet("configuration file = $inFile")
        val dir = outputDir.get().asFile
        logger.quiet("output dir = $dir")
        val className = inFile.readText().trim()
        val srcFile = File(dir, "${className}.java")
        srcFile.writeText("public class ${className} { }")
    }
}

// Create the source generation task
task<GenerateSource>("generate") {
    // Configure the locations, relative to the project and build directories
    configFile.set(project.layout.projectDirectory.file("src/config.txt"))
    outputDir.set(project.layout.buildDirectory.dir("generated-source"))
}

// Change the build directory
// Don't need to reconfigure the task properties. These are automatically updated as the build directory changes
buildDir = file("output")
Output of gradle print
> gradle print

> Task :generate
configuration file = /home/user/gradle/samples/groovy/src/config.txt
output dir = /home/user/gradle/samples/groovy/output/generated-source

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
Output of gradle print
> gradle print

> Task :generate
configuration file = /home/user/gradle/samples/kotlin/src/config.txt
output dir = /home/user/gradle/samples/kotlin/output/generated-source

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

This example creates providers that represent locations in the project and build directories through Project.getLayout() with ProjectLayout.getBuildDirectory() and ProjectLayout.getProjectDirectory().

Working with task inputs and outputs

Many builds have several tasks connected together, where one task consumes the outputs of another task as an input. To make this work, we would need to configure each task to know where to look for its inputs and place its outputs, make sure that the producing and consuming tasks are configured with the same location, and attach task dependencies between the tasks. This can be cumbersome and brittle if any of these values are configurable by a user or configured by multiple plugins, as task properties need to be configured in the correct order and locations and task dependencies kept in sync as values change.

The Property API makes this easier by keeping track of not just the value for a property, which we have seen already, but also the task that produces the value, so that you don’t have to specify it as well. As an example consider the following plugin with a producer and consumer task which are wired together:

Example 4. Implicit task dependency
build.gradle
class Producer extends DefaultTask {
    @OutputFile
    final RegularFileProperty outputFile = project.objects.fileProperty()

    @TaskAction
    void produce() {
        String message = 'Hello, World!'
        def output = outputFile.get().asFile
        output.text = message
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

class Consumer extends DefaultTask {
    @InputFile
    final RegularFileProperty inputFile = project.objects.fileProperty()

    @TaskAction
    void consume() {
        def input = inputFile.get().asFile
        def message = input.text
        logger.quiet("Read '${message}' from ${input}")
    }
}

task producer(type: Producer)
task consumer(type: Consumer)

// Connect the producer task output to the consumer task input
// Don't need to add a task dependency to the consumer task. This is automatically added
consumer.inputFile = producer.outputFile

// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
producer.outputFile = layout.buildDirectory.file('file.txt')

// Change the build directory.
// Don't need to update producer.outputFile and consumer.inputFile. These are automatically updated as the build directory changes
buildDir = 'output'
build.gradle.kts
open class Producer : DefaultTask() {
    @OutputFile
    val outputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun produce() {
        val message = "Hello, World!"
        val output = outputFile.get().asFile
        output.writeText( message)
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

open class Consumer : DefaultTask() {
    @InputFile
    val inputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun consume() {
        val input = inputFile.get().asFile
        val message = input.readText()
        logger.quiet("Read '${message}' from ${input}")
    }
}

val producer by tasks.creating(Producer::class)
val consumer by tasks.creating(Consumer::class)

// Connect the producer task output to the consumer task input
// Don't need to add a task dependency to the consumer task. This is automatically added
consumer.inputFile.set(producer.outputFile)

// Set values for the producer lazily
// Don't need to update the consumer.inputFile property. This is automatically updated as producer.outputFile changes
producer.outputFile.set(layout.buildDirectory.file("file.txt"))

// Change the build directory.
// Don't need to update producer.outputFile and consumer.inputFile. These are automatically updated as the build directory changes
buildDir = file("output")
Output of gradle consumer
> gradle consumer

> Task :producer
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/output/file.txt

> Task :consumer
Read 'Hello, World!' from /home/user/gradle/samples/groovy/output/file.txt

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
Output of gradle consumer
> gradle consumer

> Task :producer
Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/output/file.txt

> Task :consumer
Read 'Hello, World!' from /home/user/gradle/samples/kotlin/output/file.txt

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

In the example above, the task outputs and inputs are connected before any location is defined. The setters can be called at any time before the task is executed and the change will automatically affect all related input and output properties.

Another important thing to note in this example is the absence of any explicit task dependency. Task outputs represented using Providers keep track of which task produces their value, and using them as task inputs will implicitly add the correct task dependencies.

Working with collections

In this section, we are going to explore lazy collections. They work exactly like any other Provider and, just like file providers, they have additional modeling around them. There are two provider interfaces available, one for List values and another for Set values:

This type of property allows you to overwrite the entire collection value with HasMultipleValues.set(Iterable) and HasMultipleValues.set(Provider) or add new elements through the various add methods:

Just like every Provider, the collection is calculated when Provider.get() is called. The following example shows the ListProperty in action:

Example 5. List property
build.gradle
class Producer extends DefaultTask {
    @OutputFile
    final RegularFileProperty outputFile = project.objects.fileProperty()

    @TaskAction
    void produce() {
        String message = 'Hello, World!'
        def output = outputFile.get().asFile
        output.text = message
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

class Consumer extends DefaultTask {
    @InputFiles
    final ListProperty<RegularFile> inputFiles = project.objects.listProperty(RegularFile).empty()

    @TaskAction
    void consume() {
        inputFiles.get().each { inputFile ->
            def input = inputFile.asFile
            def message = input.text
            logger.quiet("Read '${message}' from ${input}")
        }
    }
}

task producerOne(type: Producer)
task producerTwo(type: Producer)
task consumer(type: Consumer)

// Connect the producer task outputs to the consumer task input
// Don't need to add task dependencies to the consumer task. These are automatically added
consumer.inputFiles.add(producerOne.outputFile)
consumer.inputFiles.add(producerTwo.outputFile)

// Set values for the producer tasks lazily
// Don't need to update the consumer.inputFiles property. This is automatically updated as producer.outputFile changes
producerOne.outputFile = layout.buildDirectory.file('one.txt')
producerTwo.outputFile = layout.buildDirectory.file('two.txt')

// Change the build directory.
// Don't need to update the task properties. These are automatically updated as the build directory changes
buildDir = 'output'
build.gradle.kts
open class Producer : DefaultTask() {
    @OutputFile
    val outputFile: RegularFileProperty = project.objects.fileProperty()

    @TaskAction
    fun produce() {
        val message = "Hello, World!"
        val output = outputFile.get().asFile
        output.writeText( message)
        logger.quiet("Wrote '${message}' to ${output}")
    }
}

open class Consumer : DefaultTask() {
    @InputFiles
    val inputFiles: ListProperty<RegularFile> = project.objects.listProperty(RegularFile::class).empty()

    @TaskAction
    fun consume() {
        inputFiles.get().forEach { inputFile ->
            val input = inputFile.asFile
            val message = input.readText()
            logger.quiet("Read '${message}' from ${input}")
        }
    }
}

val producerOne by tasks.creating(Producer::class)
val producerTwo by tasks.creating(Producer::class)
val consumer by tasks.creating(Consumer::class)

// Connect the producer task outputs to the consumer task input
// Don't need to add task dependencies to the consumer task. These are automatically added
consumer.inputFiles.add(producerOne.outputFile)
consumer.inputFiles.add(producerTwo.outputFile)

// Set values for the producer tasks lazily
// Don't need to update the consumer.inputFiles property. This is automatically updated as producer.outputFile changes
producerOne.outputFile.set(layout.buildDirectory.file("one.txt"))
producerTwo.outputFile.set(layout.buildDirectory.file("two.txt"))

// Change the build directory.
// Don't need to update the task properties. These are automatically updated as the build directory changes
buildDir = file("output")
Output of gradle consumer
> gradle consumer

> Task :producerOne
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/output/one.txt

> Task :producerTwo
Wrote 'Hello, World!' to /home/user/gradle/samples/groovy/output/two.txt

> Task :consumer
Read 'Hello, World!' from /home/user/gradle/samples/groovy/output/one.txt
Read 'Hello, World!' from /home/user/gradle/samples/groovy/output/two.txt

BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 executed
Output of gradle consumer
> gradle consumer

> Task :producerOne
Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/output/one.txt

> Task :producerTwo
Wrote 'Hello, World!' to /home/user/gradle/samples/kotlin/output/two.txt

> Task :consumer
Read 'Hello, World!' from /home/user/gradle/samples/kotlin/output/one.txt
Read 'Hello, World!' from /home/user/gradle/samples/kotlin/output/two.txt

BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 executed

Making a property unmodifiable

Most properties of a task or project are intended to be configured by plugins or build scripts and then the resulting value used to do something useful. For example, a property that specifies the output directory for a compilation task may start off with a value specified by a plugin, then a build script might configure the value to some custom location, then this value is used by the task when it runs. However, once the task starts to run, we want to prevent any further change to the property. This way we avoid errors that result from different consumers, such as the task action or Gradle’s up-to-date checks or build caching or other tasks, using different values for the property.

Lazy properties provide a finalizeValue() method to make this explicit. Calling this method makes a property instance unmodifiable from that point on and any further attempts to change the value of the property will fail. Gradle automatically makes the properties of a task final when the task starts execution.

Guidelines

This section will introduce guidelines to be successful with the Provider API. To see those guidelines in action, have a look at gradle-site-plugin, a Gradle plugin demonstrating established techniques and practices for plugin development.

  • The Property and Provider types have all of the overloads you need to query or configure a value. For this reason, you should follow the following guidelines:

    • For configurable properties, expose the Property directly through a single getter.

    • For non-configurable properties, expose an Provider directly through a single getter.

  • Avoid simplifying calls like obj.getProperty().get() and obj.getProperty().set(T) in your code by introducing additional getters and setters.

  • When migrating your plugin to use providers, follow these guidelines:

    • If it’s a new property, expose it as a Property or Provider using a single getter.

    • If it’s incubating, change it to use a Property or Provider using a single getter.

    • If it’s a stable property, add a new Property or Provider and deprecate the old one. You should wire the old getter/setters into the new property as appropriate.

Future development

Going forward, new properties will use the Provider API. The Groovy Gradle DSL adds convenience methods to make the use of Providers mostly transparent in build scripts. Existing tasks will have their existing "raw" properties replaced by Providers as needed and in a backwards compatible way. New tasks will be designed with the Provider API.

The Provider API is incubating. Please create new issues at gradle/gradle to report bugs or to submit use cases for new features.

Property Files API Reference

Use these types for mutable values:

Lazy Collections API Reference

Use these types for mutable values:

ListProperty<T>

a property whose value is List<T>

SetProperty<T>

a property whose value is Set<T>

Lazy Objects API Reference

Use these types for read only values:

Provider<T>

a property whose value is an instance of T

Factories

Use these types for mutable values:

Property<T>

a property whose value is an instance of T