Shared build services allow tasks to share state or resources. For example, tasks might share a cache of pre-computed values or use a web service or database instance.

A build service is an object that holds the state for tasks to use. It provides an alternative mechanism for hooking into a Gradle build and receiving information about task execution and operation completion.

Build services are configuration cacheable.

Gradle manages the service lifecycle, creating the service instance only when required and cleaning it up when no longer needed. Gradle can also coordinate access to the build service, ensuring that no more than a specified number of tasks use the service concurrently.

Implementing a build service

To implement a build service, create an abstract class that implements BuildService. Then, define methods you want the tasks to use on this type.

abstract class BaseCountingService implements BuildService<CountingParams>, AutoCloseable {

}

A build service implementation is treated as a custom Gradle type and can use any of the features available to custom Gradle types.

A build service can optionally take parameters, which Gradle injects into the service instance when creating it. To provide parameters, you define an abstract class (or interface) that holds the parameters. The parameters type must implement (or extend) BuildServiceParameters. The service implementation can access the parameters using this.getParameters(). The parameters type is also a custom Gradle type.

When the build service does not require any parameters, you can use BuildServiceParameters.None as the type of parameter.

interface CountingParams extends BuildServiceParameters {
    Property<Integer> getInitial()
}

A build service implementation can also optionally implement AutoCloseable, in which case Gradle will call the build service instance’s close() method when it discards the service instance. This happens sometime between the completion of the last task that uses the build service and the end of the build.

Here is an example of a service that takes parameters and is closeable:

WebServer.java
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;

import java.net.URI;
import java.net.URISyntaxException;

public abstract class WebServer implements BuildService<WebServer.Params>, AutoCloseable {

    // Some parameters for the web server
    interface Params extends BuildServiceParameters {
        Property<Integer> getPort();

        DirectoryProperty getResources();
    }

    private final URI uri;

    public WebServer() throws URISyntaxException {
        // Use the parameters
        int port = getParameters().getPort().get();
        uri = new URI(String.format("https://localhost:%d/", port));

        // Start the server ...

        System.out.println(String.format("Server is running at %s", uri));
    }

    // A public method for tasks to use
    public URI getUri() {
        return uri;
    }

    @Override
    public void close() {
        // Stop the server ...
    }
}

Note that you should not implement the BuildService.getParameters() method, as Gradle will provide an implementation of this.

A build service implementation must be thread-safe, as it will potentially be used by multiple tasks concurrently.

Using a build service in a task

To use a build service from a task, you need to:

  1. Add a property to the task of type Property<MyServiceType>.

  2. Annotate the property with @Internal or @ServiceReference (since 8.0).

  3. Assign a shared build service provider to the property (optional, when using @ServiceReference(<serviceName>)).

  4. Declare the association between the task and the service so Gradle can properly honor the build service lifecycle and its usage constraints (also optional when using @ServiceReference).

Note that using a service with any other annotation is currently not supported. For example, it is currently impossible to mark a service as an input to a task.

Annotating a shared build service property with @Internal

When you annotate a shared build service property with @Internal, you need to do two more things:

  1. Explicitly assign a build service provider obtained when registering the service with BuildServiceRegistry.registerIfAbsent() to the property.

  2. Explicitly declare the association between the task and the service via the Task.usesService.

Here is an example of a task that consumes the previous service via a property annotated with @Internal:

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // This property provides access to the service instance
    @Internal
    abstract Property<WebServer> getServer();

    @OutputFile
    abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void download() {
        // Use the server to download a file
        WebServer server = getServer().get();
        URI uri = server.getUri().resolve("somefile.zip");
        System.out.println(String.format("Downloading %s", uri));
    }
}

Annotating a shared build service property with @ServiceReference

The @ServiceReference annotation is an incubating API and is subject to change in a future release.

Otherwise, when you annotate a shared build service property with @ServiceReference, there is no need to declare the association between the task and the service explicitly; also, if you provide a service name to the annotation, and a shared build service is registered with that name, it will be automatically assigned to the property when the task is created.

Here is an example of a task that consumes the previous service via a property annotated with @ServiceReference:

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

import java.net.URI;

public abstract class Download extends DefaultTask {
    // This property provides access to the service instance
    @ServiceReference("web")
    abstract Property<WebServer> getServer();

    @OutputFile
    abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void download() {
        // Use the server to download a file
        WebServer server = getServer().get();
        URI uri = server.getUri().resolve("somefile.zip");
        System.out.println(String.format("Downloading %s", uri));
    }
}

Registering a build service and connecting it to a task

To create a build service, you register the service instance using the BuildServiceRegistry.registerIfAbsent() method. Registering the service does not create the service instance. This happens on demand when a task first uses the service. The service instance will not be created if no task uses the service during a build.

Currently, build services are scoped to a build, rather than a project, and these services are available to be shared by the tasks of all projects. You can access the registry of shared build services via Project.getGradle().getSharedServices().

Here is an example of a plugin that registers the previous service when the task property consuming the service is annotated with @Internal:

DownloadPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

public class DownloadPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register the service
        Provider<WebServer> serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
            // Provide some parameters
            spec.getParameters().getPort().set(5005);
        });

        project.getTasks().register("download", Download.class, task -> {
            // Connect the service provider to the task
            task.getServer().set(serviceProvider);
            // Declare the association between the task and the service
            task.usesService(serviceProvider);
            task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
        });
    }
}

The plugin registers the service and receives a Provider<WebService> back. This provider can be connected to task properties to pass the service to the task. Note that for a task property annotated with @Internal, the task property needs to (1) be explicitly assigned with the provider obtained during registation, and (2) you must tell Gradle the task uses the service via Task.usesService.

Compare that to when the task property consuming the service is annotated with @ServiceReference:

DownloadPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;

public class DownloadPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Register the service
        project.getGradle().getSharedServices().registerIfAbsent("web", WebServer.class, spec -> {
            // Provide some parameters
            spec.getParameters().getPort().set(5005);
        });

        project.getTasks().register("download", Download.class, task -> {
            task.getOutputFile().set(project.getLayout().getBuildDirectory().file("result.zip"));
        });
    }
}

As you can see, there is no need to assign the build service provider to the task, nor to declare explicitly that the task uses the service.

Using shared build services from configuration actions

Generally, build services are intended to be used by tasks, and as they usually represent some potentially expensive state to create, you should avoid using them at configuration time. However, sometimes, using the service at configuration time can make sense. This is possible; call get() on the provider.

Using a build service with the Worker API

In addition to using a build service from a task, you can use a build service from a Worker API action, an artifact transform or another build service. To do this, pass the build service Provider as a parameter of the consuming action or service, in the same way you pass other parameters to the action or service.

For example, to pass a MyServiceType service to Worker API action, you might add a property of type Property<MyServiceType> to the action’s parameters object and then connect the Provider<MyServiceType> that you receive when registering the service to this property:

Download.java
import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.TaskAction;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
import org.gradle.workers.WorkerExecutor;

import javax.inject.Inject;
import java.net.URI;

public abstract class Download extends DefaultTask {

    public static abstract class DownloadWorkAction implements WorkAction<DownloadWorkAction.Parameters> {
        interface Parameters extends WorkParameters {
            // This property provides access to the service instance from the work action
            abstract Property<WebServer> getServer();
        }

        @Override
        public void execute() {
            // Use the server to download a file
            WebServer server = getParameters().getServer().get();
            URI uri = server.getUri().resolve("somefile.zip");
            System.out.println(String.format("Downloading %s", uri));
        }
    }

    @Inject
    abstract public WorkerExecutor getWorkerExecutor();

    // This property provides access to the service instance from the task
    @ServiceReference("web")
    abstract Property<WebServer> getServer();

    @TaskAction
    public void download() {
        WorkQueue workQueue = getWorkerExecutor().noIsolation();
        workQueue.submit(DownloadWorkAction.class, parameter -> {
            parameter.getServer().set(getServer());
        });
    }
}

Currently, it is impossible to use a build service with a worker API action that uses ClassLoader or process isolation modes.

Accessing the build service concurrently

You can constrain concurrent execution when you register the service, by using the Property object returned from BuildServiceSpec.getMaxParallelUsages(). When this property has no value, which is the default, Gradle does not constrain access to the service. When this property has a value > 0, Gradle will allow no more than the specified number of tasks to use the service concurrently.

When the consuming task property is annotated with @Internal, for the constraint to take effect, the build service must be registered with the consuming task via Task.usesService(Provider<? extends BuildService<?>>). This is not necessary if, instead, the consuming property is annotated with @ServiceReference.

Receiving information about task execution

A build service can be used to receive events as tasks are executed. To do this, create and register a build service that implements OperationCompletionListener:

TaskEventsService.java
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;
import org.gradle.tooling.events.FinishEvent;
import org.gradle.tooling.events.OperationCompletionListener;
import org.gradle.tooling.events.task.TaskFinishEvent;

public abstract class TaskEventsService implements BuildService<BuildServiceParameters.None>,
    OperationCompletionListener { (1)

    @Override
    public void onFinish(FinishEvent finishEvent) {
        if (finishEvent instanceof TaskFinishEvent) { (2)
            // Handle task finish event...
        }
    }
}
1 Implement the OperationCompletionListener interface and the BuildService interface.
2 Check if the finish event is a TaskFinishEvent.

Then, in the plugin, you can use the methods on the BuildEventsListenerRegistry service to start receiving events:

TaskEventsPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
import org.gradle.build.event.BuildEventsListenerRegistry;

import javax.inject.Inject;

public abstract class TaskEventsPlugin implements Plugin<Project> {
    @Inject
    public abstract BuildEventsListenerRegistry getEventsListenerRegistry(); (1)

    @Override
    public void apply(Project project) {
        Provider<TaskEventsService> serviceProvider =
            project.getGradle().getSharedServices().registerIfAbsent(
                "taskEvents", TaskEventsService.class, spec -> {}); (2)

        getEventsListenerRegistry().onTaskCompletion(serviceProvider); (3)
    }
}
1 Use service injection to obtain an instance of the BuildEventsListenerRegistry.
2 Register the build service as usual.
3 Use the service Provider to subscribe to the build service to build events.