# Developing Custom Gradle Types

version 6.4

There are several different kinds of "add-ons" to Gradle that you can develop, such as plugins, tasks, project extensions or artifact transforms, that are all implemented as classes and other types that can run on the JVM. This chapter discusses some of the features and concepts that are common to these types. You can use these features to help implement custom Gradle types and provide a consistent DSL for your users.

This chapter applies to the following types:

- Plugin types.

- Task types.

- Artifact transform parameters types.

- Worker API work action parameters types.

- Extension objects created using `ExtensionContainer.create()`, for example a project extension registered by a plugin.

- Objects created using `ObjectFactory.newInstance()`.

- Objects created for a managed nested property.

- Elements of a `NamedDomainObjectContainer`.

## Configuration using bean properties

The custom Gradle types that you implement often hold some configuration that you want to make available to build scripts and other plugins. For example, a download task may have configuration that specifies the URL to download from and the file system location to write the result to. This configuration is represented as Java bean properties.

Kotlin and Groovy provide conveniences for declaring Java bean properties, which make them good language choices to use to implement Gradle types. These conveniences are demonstrated in the samples below.

Gradle also provides some conveniences for implementing types with bean properties.

### Managed properties

Gradle can provide an implementation of an abstract property. This is called a managed property, as Gradle takes care of managing the state of the property. A property may be mutable, meaning that it has both a getter method and setter method, or read-only, meaning that it has only a getter method.

| Managed properties are currently an incubating feature.

#### Mutable managed properties

To declare a mutable managed property, add an abstract getter method and an abstract setter method for the property to the type.

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

Example 1. Mutable managed property
Download.java

    import org.gradle.api.DefaultTask;
    import org.gradle.api.tasks.Input;
    import org.gradle.api.tasks.TaskAction;

    import java.net.URI;

    public abstract class Download extends DefaultTask {
        // Use an abstract getter and setter method
        @Input
        abstract URI getUri();
        abstract void setUri(URI uri);

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

Download.kt

    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.TaskAction
    import java.net.URI

    abstract class Download : DefaultTask() {
        // Use an abstract var
        @get:Input
        abstract var uri: URI

        @TaskAction
        fun run() {
            // Use the `uri` property
            println("Downloading $uri")
        }
    }

Download.groovy

    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.TaskAction

    abstract class Download extends DefaultTask {
        // Use an abstract property
        @Input
        abstract URI uri

        @TaskAction
        void run() {
            // Use the `uri` property
            println "downloading ${uri}"
        }
    }

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

#### Read-only managed properties

To declare a read-only managed property, add an abstract getter method for the property to the type. The property should not have any setter methods. Gradle will provide an implementation of the getter and also create a value for the property.

This is a useful pattern to use with one of Gradle’s configurable lazy property or container types.

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

Example 2. Read-only managed property
Download.java

    import org.gradle.api.DefaultTask;
    import org.gradle.api.provider.Property;
    import org.gradle.api.tasks.Input;
    import org.gradle.api.tasks.TaskAction;

    import java.net.URI;

    public abstract class Download extends DefaultTask {
        // Use an abstract getter method
        @Input
        abstract Property<URI> getUri();

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

Download.kt

    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.TaskAction
    import org.gradle.api.provider.Property
    import java.net.URI

    abstract class Download : DefaultTask() {
        // Use an abstract val
        @get:Input
        abstract val uri: Property<URI>

        @TaskAction
        fun run() {
            // Use the `uri` property
            println("Downloading ${uri.get()}")
        }
    }

Download.groovy

    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.TaskAction
    import org.gradle.api.provider.Property
    import org.gradle.api.tasks.TaskAction

    abstract class Download extends DefaultTask {
        // Use an abstract getter method
        @Input
        abstract Property<URI> getUri()

        @TaskAction
        void run() {
            // Use the `uri` property
            println "downloading ${uri.get()}"
        }
    }

Note that for a property to be considered a read only managed property, all of the property’s getter methods must be `abstract` and have `public` or `protected` visibility. The property must not have any setter methods. In addition, the property type must have one of the following:

- `Property<T>`

- `RegularFileProperty`

- `DirectoryProperty`

- `ListProperty<T>`

- `SetProperty<T>`

- `MapProperty<K, V>`

- `ConfigurableFileCollection`

- `ConfigurableFileTree`

- `DomainObjectSet<T>`

- `NamedDomainObjectContainer<T>`

Gradle creates values for read-only managed properties in the same way as ObjectFactory.

#### Read-only managed nested properties

To declare a read-only managed nested property, add an abstract getter method for the property to the type annotated with `@Nested`. The property should not have any setter methods. Gradle provides an implementation for the getter method, and also creates a value for the property. The nested type is also treated as a custom type, and can use the features discussed in this chapter.

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:

Example 3. Read-only managed nested property
Download.java

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

        @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();
    }

Download.kt

    abstract class Download : DefaultTask() {
        // Use an abstract getter method annotated with @Nested
        @get:Nested
        abstract val resource: Resource

        @TaskAction
        fun run() {
            // Use the `resource` property
            println("Downloading https://${resource.hostName.get()}/${resource.path.get()}")
        }
    }

    interface Resource {
        @get:Input
        val hostName: Property<String>
        @get:Input
        val path: Property<String>
    }

Download.groovy

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

        @TaskAction
        void run() {
            // Use the `resource` property
            println("Downloading https://${resource.hostName.get()}/${resource.path.get()}")
        }
    }

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

Note that for a property to be considered a read only managed nested property, all of the property’s getter methods must be `abstract` and have `public` or `protected` visibility. The property must not have any setter methods. In addition, the property getter must be annotated with `@Nested`.

### Managed types

A managed type is an abstract class or interface with no fields and whose properties are all managed. That is, it is a type whose state is entirely managed by Gradle.

## DSL support and extensibility

When Gradle creates an instance of a custom type, it decorates the instance to mix-in DSL and extensibility support.

Each decorated instance implements ExtensionAware, and so can have extension objects attached to it.

Note that plugins and the elements of containers created using Project.container() are currently not decorated, due to backwards compatibility issues.

## Service injection

Gradle provides a number of useful services that can be used by custom Gradle types. For example, the WorkerExecutor service can be used by a task to run work in parallel, as seen in the worker API section. The services are made available through service injection.

### Available services

The following services are available for injection:

- ObjectFactory - Allows model objects to be created. See Creating nested objects for more details.

- ProjectLayout - Provides access to key project locations. See lazy configuration for more details. This service is unavailable in Worker API actions.

- ProviderFactory - Creates `Provider` instances. See lazy configuration for more details.

- WorkerExecutor - Allows a task to run work in parallel. See the worker API for more details.

- FileSystemOperations - Allows a task to run operations on the filesystem such as deleting files, copying files or syncing directories.

- ExecOperations - Allows a task to run external processes with dedicated support for running external `java` programs.

Out of the above, `ProjectLayout` and `WorkerExecutor` services are only available for injection in project plugins.

### Constructor injection

There are 2 ways that an object can receive the services that it needs. The first option is to add the service as a parameter of the class constructor. The constructor must be annotated with the `javax.inject.Inject` annotation. Gradle uses the declared type of each constructor parameter to determine the services that the object requires. The order of the constructor parameters and their names are not significant and can be whatever you like.

Here is an example that shows a task type that receives an `ObjectFactory` via its constructor:

Example 4. Constructor service injection
Download.java

    import org.gradle.api.DefaultTask;
    import org.gradle.api.file.DirectoryProperty;
    import org.gradle.api.model.ObjectFactory;
    import org.gradle.api.tasks.OutputDirectory;
    import org.gradle.api.tasks.TaskAction;

    import javax.inject.Inject;

    public class Download extends DefaultTask {
        private final DirectoryProperty outputDirectory;

        // Inject an ObjectFactory into the constructor
        @Inject
        public Download(ObjectFactory objectFactory) {
            // Use the factory
            outputDirectory = objectFactory.directoryProperty();
        }

        @OutputDirectory
        public DirectoryProperty getOutputDirectory() {
            return outputDirectory;
        }

        @TaskAction
        void run() {
            // ...
        }
    }

Download.kt

    import javax.inject.Inject
    import org.gradle.api.model.ObjectFactory
    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.TaskAction
    import org.gradle.api.file.DirectoryProperty
    import org.gradle.api.tasks.OutputDirectory

    open class Download
    // Inject an ObjectFactory into the constructor
    @Inject constructor(objectFactory: ObjectFactory) : DefaultTask() {
        // Use the factory
        @OutputDirectory
        val outputDirectory = objectFactory.directoryProperty()

        @TaskAction
        fun run() {
            // ...
        }
    }

Download.groovy

    import org.gradle.api.DefaultTask
    import org.gradle.api.file.DirectoryProperty
    import org.gradle.api.model.ObjectFactory
    import org.gradle.api.tasks.OutputDirectory
    import org.gradle.api.tasks.TaskAction

    import javax.inject.Inject

    class Download extends DefaultTask {
        @OutputDirectory
        final DirectoryProperty outputDirectory

        // Inject an ObjectFactory into the constructor
        @Inject
        Download(ObjectFactory objectFactory) {
            // Use the factory
            outputDirectory = objectFactory.directoryProperty()
        }

        @TaskAction
        void run() {
            // ...
        }
    }

### Property injection

Alternatively, a service can be injected by adding a property getter method annotated with the `javax.inject.Inject` annotation to the class. This can be useful, for example, when you cannot change the constructor of the class due to backwards compatibility constraints. This pattern also allows Gradle to defer creation of the service until the getter method is called, rather than when the instance is created. This can help with performance. Gradle uses the declared return type of the getter method to determine the service to make available. The name of the property is not significant and can be whatever you like.

The property getter method must be `public` or `protected`. The method can be `abstract` or, in cases where this isn’t possible, can have a dummy method body. The method body is discarded.

Here is an example that shows a task type that receives a two services via property getter methods:

Example 5. Property service injection
Download.java

    import javax.inject.Inject;
    import org.gradle.api.model.ObjectFactory;
    import org.gradle.api.DefaultTask;
    import org.gradle.api.tasks.TaskAction;
    import org.gradle.workers.WorkerExecutor;

    public abstract class Download extends DefaultTask {
        // Use an abstract getter method
        @Inject
        protected abstract ObjectFactory getObjectFactory();

        // Alternatively, use a getter method with a dummy implementation
        @Inject
        protected WorkerExecutor getWorkerExecutor() {
            // Method body is ignored
            throw new UnsupportedOperationException();
        }

        @TaskAction
        void run() {
            WorkerExecutor workerExecutor = getWorkerExecutor();
            ObjectFactory objectFactory = getObjectFactory();
            // Use the executor and factory ...
        }
    }

Download.kt

    import javax.inject.Inject
    import org.gradle.api.model.ObjectFactory
    import org.gradle.api.DefaultTask
    import org.gradle.api.tasks.TaskAction
    import org.gradle.workers.WorkerExecutor

    abstract class Download : DefaultTask() {
        // Use an abstract property
        // Note that the @Inject annotation must be attached to the getter
        @get:Inject
        abstract val objectFactory: ObjectFactory

        // Alternatively, use a property getter with a dummy implementation
        // Note that the property must be open and the @Inject annotation must be attached to the getter
        @get:Inject
        open val workerExecutor: WorkerExecutor
            get() {
                // Getter body is ignored
                throw UnsupportedOperationException()
            }

        @TaskAction
        fun run() {
            // Use the executor and factory ...
        }
    }

Download.groovy

    import org.gradle.api.DefaultTask
    import org.gradle.api.model.ObjectFactory
    import org.gradle.api.tasks.TaskAction
    import org.gradle.workers.WorkerExecutor

    import javax.inject.Inject

    abstract class Download extends DefaultTask {
        // Use an abstract getter method
        @Inject
        protected abstract ObjectFactory getObjectFactory()

        // Alternatively, use a getter method with a dummy implementation
        @Inject
        protected WorkerExecutor getWorkerExecutor() {
            // Method body is ignored
            throw new UnsupportedOperationException()
        }

        @TaskAction
        void run() {
            // Use the executor and factory ...
        }
    }

## Creating nested objects

A custom Gradle type can use the ObjectFactory service to create instances of Gradle types to use for its property values. These instances can make use of the features discussed in this chapter, allowing you to create 'nested' object and a nested DSL.

You can also have Gradle create nested objects for you by using a managed nested property.

In the following example, a project extension receives an `ObjectFactory` instance through its constructor. The constructor uses this to create a nested `Resource` object (also a custom Gradle type) and makes this object available through the `resource` property.

Example 6. Nested object creation
DownloadExtension.java

    import org.gradle.api.model.ObjectFactory;

    import javax.inject.Inject;

    public class DownloadExtension {
        // A nested instance
        private final Resource resource;

        @Inject
        public DownloadExtension(ObjectFactory objectFactory) {
            // Use an injected ObjectFactory to create a Resource object
            resource = objectFactory.newInstance(Resource.class);
        }

        public Resource getResource() {
            return resource;
        }
    }

    public class Resource {
        private URI uri;

        public URI getUri() {
            return uri;
        }
        public void setUri(URI uri) {
            this.uri = uri;
        }
    }

DownloadExtension.kt

    import javax.inject.Inject
    import org.gradle.api.model.ObjectFactory

    open class DownloadExtension @Inject constructor(objectFactory: ObjectFactory) {
        // Use an injected ObjectFactory to create a nested Resource object
        val resource: Resource = objectFactory.newInstance(Resource::class.java)
    }

    open class Resource {
        var uri: URI? = null
    }

DownloadExtension.groovy

    import org.gradle.api.model.ObjectFactory

    import javax.inject.Inject

    class DownloadExtension {
        // A nested instance
        final Resource resource

        @Inject
        DownloadExtension(ObjectFactory objectFactory) {
            // Use an injected ObjectFactory to create a Resource object
            resource = objectFactory.newInstance(Resource)
        }
    }

    class Resource {
        URI uri
    }

## Collection types

Gradle provides types for maintaining collections of objects, intended to work well with the Gradle DSL and provide useful features such as lazy configuration.

### NamedDomainObjectContainer

A NamedDomainObjectContainer manages a set of objects, where each element has a name associated with it. The container takes care of creating and configuring the elements, and provides a DSL that build scripts can use to define and configure elements. It is intended to hold objects which are themselves configurable, for example a set of custom Gradle objects.

Gradle uses `NamedDomainObjectContainer` type extensively throughout the API. For example, the `project.tasks` object used to manage the tasks of a project is a `NamedDomainObjectContainer<Task>`.

You can create a container instance using the ObjectFactory service, which provides the ObjectFactory.domainObjectContainer() method. This is also available using the Project.container() method, however in a custom Gradle type it’s generally better to use the injected `ObjectFactory` service instead of passing around a `Project` instance.

You can also create a container instance using a read-only managed property, described above.

In order to use a type with any of the `domainObjectContainer()` methods, it must expose a property named “name” as the unique, and constant, name for the object. The `domainObjectContainer(Class)` variant of the method creates new instances by calling the constructor of the class that takes a string argument, which is the desired name of the object. Objects created this way are treated as custom Gradle types, and so can make use of the features discussed in this chapter, for example service injection or managed properties.

See the above link for `domainObjectContainer()` method variants that allow custom instantiation strategies.

Example 7. Managing a collection of objects
DownloadExtension.java

    import org.gradle.api.NamedDomainObjectContainer;
    import org.gradle.api.model.ObjectFactory;

    import javax.inject.Inject;

    public class DownloadExtension {
        // A container of `Resource` objects
        private final NamedDomainObjectContainer<Resource> resources;

        @Inject
        public DownloadExtension(ObjectFactory objectFactory) {
            // Use an injected ObjectFactory to create a container
            resources = objectFactory.domainObjectContainer(Resource.class);
        }

        public NamedDomainObjectContainer<Resource> getResources() {
            return resources;
        }
    }

    public class Resource {
        private final String name;
        private URI uri;
        private String userName;

        // Type must have a public constructor that takes the element name as a parameter
        public Resource(String name) {
            this.name = name;
        }

        // Type must have a 'name' property, which should be read-only
        public String getName() {
            return name;
        }

        public URI getUri() {
            return uri;
        }
        public void setUri(URI uri) {
            this.uri = uri;
        }

        public String getUserName() {
            return userName;
        }
        public void setUserName(String userName) {
            this.userName = userName;
        }
    }

DownloadExtension.kt

    import javax.inject.Inject
    import org.gradle.api.model.ObjectFactory
    import org.gradle.api.NamedDomainObjectContainer

    open class DownloadExtension @Inject constructor(objectFactory: ObjectFactory) {
        // Use an injected ObjectFactory to create a container of `Resource` objects
        val resources = objectFactory.domainObjectContainer(Resource::class.java)
    }

    // Type must have a public constructor that takes the element name as a parameter
    // Type must have a 'name' property, which should be read-only
    open class Resource(val name: String) {
        var uri: URI? = null
        var userName: String? = null
    }

DownloadExtension.groovy

    import org.gradle.api.NamedDomainObjectContainer
    import org.gradle.api.model.ObjectFactory

    import javax.inject.Inject

    class DownloadExtension {
        // A container of `Resource` instances
        final NamedDomainObjectContainer<Resource> resources

        @Inject
        DownloadExtension(ObjectFactory objectFactory) {
            // Use an injected ObjectFactory to create a container
            resources = objectFactory.domainObjectContainer(Resource)
        }
    }

    class Resource {
        // Type must have a 'name' property, which should be read-only
        final String name
        URI uri
        String userName

        // Type must have a public constructor that takes the element name as a parameter
        Resource(String name) {
            this.name = name
        }
    }

For each container property, Gradle automatically adds a block to the Groovy and Kotlin DSL that you can use to configure the contents of the container:

Example 8. Configure block
build.gradle.kts

    plugins {
        id("org.gradle.sample.download")
    }

    download {
        // Can use a block to configure the container contents
        resources {
            register("gradle") {
                uri = uri("https://gradle.org")
            }
        }
    }

build.gradle

    plugins {
        id("org.gradle.sample.download")
    }

    download {
        // Can use a block to configure the container contents
        resources {
            gradle {
                uri = uri('https://gradle.org')
            }
        }
    }

### ExtensiblePolymorphicDomainObjectContainer

An ExtensiblePolymorphicDomainObjectContainer is a `NamedDomainObjectContainer` that allows you to define instantiation strategies for different types of objects.

You can create an instance using the ObjectFactory.polymorphicDomainObjectContainer() method.

### NamedDomainObjectSet

A NamedDomainObjectSet holds a set of configurable objects, where each element has a name associated with it. This is similar to `NamedDomainObjectContainer`, however a `NamedDomainObjectSet` doesn’t manage the objects in the collection. They need to be created and added manually.

You can create an instance using the ObjectFactory.namedDomainObjectSet() method.

### NamedDomainObjectList

A NamedDomainObjectList holds a list of configurable objects, where each element has a name associated with it. This is similar to `NamedDomainObjectContainer`, however a `NamedDomainObjectList` doesn’t manage the objects in the collection. They need to be created and added manually.

You can create an instance using the ObjectFactory.namedDomainObjectList() method.

### DomainObjectSet

A DomainObjectSet simply holds a set of configurable objects. Compared to `NamedDomainObjectContainer`, a `DomainObjectSet` doesn’t manage the objects in the collection. They need to be created and added manually.

You can create an instance using the ObjectFactory.domainObjectSet() method.