skip to content

@ServiceReference Injection

Auto-wiring a shared build service into a task with @ServiceReference, and services that observe build operations. Asked as the ergonomic follow-up once you can register a BuildService at all.

on this pageshow

questions

5

What does the @ServiceReference annotation do on a Gradle task, and why is it preferable to manually wiring a build service?

level: middleimportance: must knowfreq 45%

answer

  1. abstract Property<MyService> on task
  2. auto-wire registered build service
  3. no manual usesService()
  4. optional name match -> may be undefined
  5. config-cache friendly

basics

~10 s

@ServiceReference marks an abstract Property<MyService> on a task so Gradle auto-injects a registered shared build service and automatically tracks it as a task dependency, so you don't call usesService() yourself.

solid answer

~40 s

A shared **BuildService** is a long-lived object you register once and reuse across tasks (e.g. a connection pool or counter). Normally a task declares an abstract `Property<MyService>`, you assign it the `Provider` from registration, and you call `task.usesService(provider)` so Gradle knows the task uses it (important for `--max-workers` constraints). `@ServiceReference` collapses that: you annotate the abstract property, optionally with a service name, and Gradle **auto-wires** the matching registered service by type (or name) and **implicitly marks the task as using it**. This removes boilerplate and the easy-to-forget `usesService()` call. If you supply a name and no service with that name is registered, the property stays undefined — so the task must handle absence (e.g. via `@Optional`). It's the configuration-cache-friendly, declarative way to consume a build service from a task.

code

kotlin · 7 lines
kotlin
abstract class GreetTask : DefaultTask() {
    @get:ServiceReference("greeter")
    abstract val greeter: Property<GreeterService>

    @TaskAction
    fun greet() = logger.lifecycle(greeter.get().hello())
}

go deeper

for a junior

Know it auto-injects a shared build service into an abstract Property on a task so you don't wire it manually.

for a middle

Explain that it replaces both the provider assignment and usesService(), and that a named reference may resolve to nothing.

for a senior

Discuss the parallelism implication (maxParallelUsages / usesService) and configuration-cache friendliness of the lazy property.

for a principal

Frame it as the declarative consumption surface for cross-task shared state in a plugin's API, and how naming conventions keep services discoverable across a multi-plugin build.

## What a build service is A **shared build service** is an object whose lifecycle Gradle manages for the whole build. You implement `BuildService<Params>` (or `BuildService<BuildServiceParameters.None>`), register it once, and many tasks can share the same instance. Typical uses: a shared counter, an in-memory cache, a pool of expensive connections, or a wrapper around an external process. ## The manual wiring it replaces Without `@ServiceReference`, consuming a service from a task takes three steps: 1. Register the service, getting a `Provider<MyService>`. 2. Declare an abstract `@get:Internal abstract val server: Property<MyService>` on the task. 3. Assign the provider **and** call `task.usesService(provider)`. That last call is the part people forget. `usesService()` tells Gradle the task *uses* this service, which matters because a service can declare `getMaxParallelUsages()` — Gradle then constrains how many tasks using it run concurrently against `--max-workers`. Forget `usesService()` and that throttling silently doesn't apply. ## What @ServiceReference does `@ServiceReference` is a property annotation. You put it on the abstract `Property<MyService>`: - Gradle **auto-injects** the registered service of the matching type when the task runs. - It **implicitly registers the usage** (no manual `usesService()`), so parallelism limits and dependency ordering apply. - You can pass a **name**: `@ServiceReference("webServer")`. Then Gradle matches the registration by that name. If nothing with that name is registered, the property is simply left without a value. Because matching can fail (named service not registered), treat the property as possibly-absent: combine with `@Optional` or guard with `.isPresent`. ## Configuration cache fit The property is a lazy `Provider`/`Property`, so the service instance is resolved at execution time, not stored eagerly — which is exactly what the configuration cache wants. ```kotlin abstract class CountTask : DefaultTask() { @get:ServiceReference("counter") abstract val counter: Property<CounterService> @TaskAction fun run() { val n = counter.get().increment() logger.lifecycle("count = $n") } } ``` Here the `counter` service is wired by name and the task is automatically marked as using it.

  • What does @ServiceReference save you from calling manually?
    The explicit Task.usesService(provider) call — it implicitly registers the task's usage of the service so parallelism limits and ordering apply.
  • What happens if you give @ServiceReference a name that no registered service matches?
    The property is left without a value. The task must tolerate absence (e.g. pair with @Optional or check isPresent); accessing .get() would fail.

saying these in an interview costs you the question

  • Claiming @ServiceReference registers the service itself — it only consumes an already-registered service.
  • Saying you still must call usesService() alongside @ServiceReference — that's the boilerplate it removes.

context

open as a page

When consuming a build service via @ServiceReference, why and how should a task guard against the service being absent?

level: middleimportance: should knowfreq 20%

basics

~10 s

A named @ServiceReference may match no registered service, leaving the property unset. Pair it with @Optional and check counter.isPresent (or use orElse) before calling .get(), so the task fails gracefully instead of throwing.

open as a page

How does Gradle resolve which registered build service to inject for a @ServiceReference, with and without an explicit name?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Without a name, Gradle injects the single registered service whose type matches the property's type. With a name, it matches the registration name; if none matches, the property stays undefined rather than failing.

open as a page

How do you implement a build-listener build service that observes task execution events via OperationCompletionListener, and how is it registered?

level: seniorimportance: should knowfreq 22%

basics

~10 s

Make a BuildService also implement OperationCompletionListener, override onFinish(event) to inspect TaskFinishEvent results, then register it with BuildEventsListenerRegistry.onTaskCompletion(provider). This is the configuration-cache-safe replacement for legacy TaskExecutionListener.

open as a page

Why does Gradle need to know a task 'uses' a build service, and how does @ServiceReference satisfy that requirement?

level: seniorimportance: should knowfreq 28%

basics

~10 s

Gradle throttles concurrent tasks against a service's maxParallelUsages limit, but only for tasks that declare they use it. @ServiceReference implicitly declares that usage, so the limit and lifecycle tracking apply automatically.

open as a page