Gradle provides properties that are important for lazy configuration. When implementing a custom task or plugin, it’s imperative that you use these lazy properties.

prop prov 1

Gradle represents lazy properties with two interfaces:

  1. Property - Represents a value that can be queried and changed.

  2. Provider - Represents a value that can only be queried and cannot be changed.

Properties and providers manage values and configurations in a build script.

In this example, configuration is a Property<String> that is set to the configurationProvider Provider<String>. The configurationProvider lazily provides the value "Hello, Gradle!":

build.gradle.kts
abstract class MyIntroTask : DefaultTask() {
    @get:Input
    abstract val configuration: Property<String>

    @TaskAction
    fun printConfiguration() {
        println("Configuration value: ${configuration.get()}")
    }
}

val configurationProvider: Provider<String> = project.provider { "Hello, Gradle!" }

tasks.register("myIntroTask", MyIntroTask::class) {
    configuration.set(configurationProvider)
}
build.gradle
abstract class MyIntroTask extends DefaultTask {
    @Input
    abstract Property<String> getConfiguration()

    @TaskAction
    void printConfiguration() {
        println "Configuration value: ${configuration.get()}"
    }
}

Provider<String> configurationProvider = project.provider { "Hello, Gradle!" }

tasks.register("myIntroTask", MyIntroTask) {
    it.setConfiguration(configurationProvider)
}

Understanding Properties

Properties in Gradle are variables that hold values. They can be defined and accessed within the build script to store information like file paths, version numbers, or custom values.

Properties can be set and retrieved using the project object:

build.gradle.kts
// Setting a property
val simpleMessageProperty: Property<String> = project.objects.property(String::class)
simpleMessageProperty.set("Hello, World from a Property!")
// Accessing a property
println(simpleMessageProperty.get())
build.gradle
// Setting a property
def simpleMessageProperty = project.objects.property(String)
simpleMessageProperty.set("Hello, World from a Property!")
// Accessing a property
println(simpleMessageProperty.get())

Properties:

  • 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).

Understanding Providers

Providers are objects that represent a value that may not be immediately available. Providers are useful for lazy evaluation and can be used to model values that may change over time or depend on other tasks or inputs:

build.gradle.kts
// Setting a provider
val simpleMessageProvider: Provider<String> = project.providers.provider { "Hello, World from a Provider!" }
// Accessing a provider
println(simpleMessageProvider.get())
build.gradle
// Setting a provider
def simpleMessageProvider = project.providers.provider { "Hello, World from a Provider!" }
// Accessing a provider
println(simpleMessageProvider.get())

Providers:

  • 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 wherever a Provider is required.

Using Gradle Managed Properties

Gradle’s managed properties allow you to declare properties as abstract getters (Java, Groovy) or abstract properties (Kotlin).

Gradle then automatically provides the implementation for these properties, managing their state.

A property may be mutable, meaning that it has both a get() method and set() method:

build.gradle.kts
abstract class MyPropertyTask : DefaultTask() {
    @get:Input
    abstract val messageProperty: Property<String> // message property

    @TaskAction
    fun printMessage() {
        println(messageProperty.get())
    }
}

tasks.register<MyPropertyTask>("myPropertyTask") {
    messageProperty.set("Hello, Gradle!")
}
build.gradle
abstract class MyPropertyTask extends DefaultTask {
    @Input
    abstract Property<String> messageProperty = project.objects.property(String)

    @TaskAction
    void printMessage() {
        println(messageProperty.get())
    }
}

tasks.register('myPropertyTask', MyPropertyTask) {
    messageProperty.set("Hello, Gradle!")
}

Or read-only, meaning that it has only a get() method. The read-only properties are providers:

build.gradle.kts
abstract class MyProviderTask : DefaultTask() {
    final val messageProvider: Provider<String> = project.providers.provider { "Hello, Gradle!" } // message provider

    @TaskAction
    fun printMessage() {
        println(messageProvider.get())
    }
}

tasks.register<MyProviderTask>("MyProviderTask") {

}
build.gradle
abstract class MyProviderTask extends DefaultTask {
    final Provider<String> messageProvider = project.providers.provider { "Hello, Gradle!" }

    @TaskAction
    void printMessage() {
        println(messageProvider.get())
    }
}

tasks.register('MyProviderTask', MyProviderTask)

Mutable Managed Properties

A mutable managed property is declared using an abstract getter method of type Property<T>, where T can be any serializable type or a fully managed Gradle type. The property must not have any setter methods.

Here is an example of a task type with an uri property of type URI:

Download.java
public abstract class Download extends DefaultTask {
    @Input
    public abstract Property<URI> getUri(); // abstract getter of type Property<T>

    @TaskAction
    void run() {
        System.out.println("Downloading " + getUri().get()); // Use the `uri` property
    }
}

Note that for a property to be considered a mutable managed property, the property’s getter methods must be abstract and have public or protected visibility.

The property type must be one of the following:

Property Type Note

Property<T>

Where T is typically Double, Integer, Long, String, or Bool

RegularFileProperty

Configurable regular file location, whose value is mutable

DirectoryProperty

Configurable directory location, whose value is mutable

ListProperty<T>

List of elements of type T

SetProperty<T>

Set of elements of type T

MapProperty<K, V>

Map of K type keys with V type values

ConfigurableFileCollection

A mutable FileCollection which represents a collection of file system locations

ConfigurableFileTree

A mutable FileTree which represents a hierarchy of files

Read-only Managed Properties (Providers)

You can declare a read-only managed property, also known as a provider, using a getter method of type Provider<T>. The method implementation needs to derive the value. It can, for example, derive the value from other properties.

Here is an example of a task type with a uri provider that is derived from a location property:

Download.java
public abstract class Download extends DefaultTask {
    @Input
    public abstract Property<String> getLocation();

    @Internal
    public Provider<URI> getUri() {
        return getLocation().map(l -> URI.create("https://" + l));
    }

    @TaskAction
    void run() {
        System.out.println("Downloading " + getUri().get());  // Use the `uri` provider (read-only property)
    }
}

Read-only Managed Nested Properties (Nested Providers)

You can declare a read-only managed nested property by adding an abstract getter method for the property to a type annotated with @Nested. The property should not have any setter methods. Gradle provides the implementation for the getter method and creates a value for the property.

This pattern is useful when a custom type has a nested complex type which has the same lifecycle. If the lifecycle is different, consider using Property<NestedType> instead.

Here is an example of a task type with a resource property. The Resource type is also a custom Gradle type and defines some managed properties:

Download.java
public abstract class Download extends DefaultTask {
    @Nested
    public abstract Resource getResource(); // Use an abstract getter method annotated with @Nested

    @TaskAction
    void run() {
        // Use the `resource` property
        System.out.println("Downloading https://" + getResource().getHostName().get() + "/" + getResource().getPath().get());
    }
}

public interface Resource {
    @Input
    Property<String> getHostName();
    @Input
    Property<String> getPath();
}

Read-only Managed "name" Property (Provider)

If the type contains an abstract property called "name" of type String, Gradle provides an implementation for the getter method, and extends each constructor with a "name" parameter, which comes before all other constructor parameters.

If the type is an interface, Gradle will provide a constructor with a single "name" parameter and @Inject semantics.

You can have your type implement or extend the Named interface, which defines such a read-only "name" property:

import org.gradle.api.Named

interface MyType : Named {
    // Other properties and methods...
}

class MyTypeImpl(override val name: String) : MyType {
    // Implement other properties and methods...
}

// Usage
val instance = MyTypeImpl("myName")
println(instance.name) // Prints: myName

Using Gradle Managed Types

A managed type as an abstract class or interface with no fields and whose properties are all managed. These types have their state entirely managed by Gradle.

For example, this managed type is defined as an interface:

Resource.java
public interface Resource {
    @Input
    Property<String> getHostName();
    @Input
    Property<String> getPath();
}

A named managed type is a managed type that additionally has an abstract property "name" of type String. Named managed types are especially useful as the element type of NamedDomainObjectContainer:

build.gradle.kts
interface MyNamedType {
    val name: String
}

class MyNamedTypeImpl(override val name: String) : MyNamedType

class MyPluginExtension(project: Project) {
    val myNamedContainer: NamedDomainObjectContainer<MyNamedType> =
        project.container(MyNamedType::class.java) { name ->
            project.objects.newInstance(MyNamedTypeImpl::class.java, name)
        }
}
build.gradle
interface MyNamedType {
    String getName()
}

class MyNamedTypeImpl implements MyNamedType {
    String name

    MyNamedTypeImpl(String name) {
        this.name = name
    }
}

class MyPluginExtension {
    NamedDomainObjectContainer<MyNamedType> myNamedContainer

    MyPluginExtension(Project project) {
        myNamedContainer = project.container(MyNamedType) { name ->
            new MyNamedTypeImpl(name)
        }
    }
}

Using Java Bean Properties

Sometimes you may see properties implemented in the Java bean property style. That is, they do not use a Property<T> or Provider<T> types but are instead implemented with concrete setter and getter methods (or corresponding conveniences in Groovy or Kotlin).

This style of property definition is legacy in Gradle and is discouraged:

public class MyTask extends DefaultTask {
    private String someProperty;

    public String getSomeProperty() {
        return someProperty;
    }

    public void setSomeProperty(String someProperty) {
        this.someProperty = someProperty;
    }

    @TaskAction
    public void myAction() {
        System.out.println("SomeProperty: " + someProperty);
    }
}