skip to content

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

level: middleimportance: should knowfreq 20%

answer

  1. named/type match can be empty
  2. .get() on empty -> MissingValueException
  3. @Optional on the property
  4. isPresent / getOrNull / orElse
  5. conditional registration scenario

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.

solid answer

~40 s

`@ServiceReference` does not guarantee a value: a named reference whose service isn't registered, or a type reference with no match, leaves the `Property` undefined. Calling `.get()` then throws at execution time. To author robustly, combine `@get:ServiceReference("x")` with `@get:Optional` so Gradle's input validation accepts an empty value, and in the task action branch on `prop.isPresent` — or call `prop.getOrNull()` / `prop.orElse(default)`. This matters when a plugin's service is conditionally registered (e.g. only when another plugin is applied) but a task that *might* use it is always present. The defensive pattern keeps the task usable in both configurations rather than hard-failing builds that never registered the optional service.

code

kotlin · 9 lines
kotlin
@get:ServiceReference("metrics")
@get:Optional
abstract val metrics: Property<MetricsService>

@TaskAction
fun run() {
    metrics.getOrNull()?.flush()
        ?: logger.info("no metrics service registered")
}

go deeper

for a junior

Know a referenced service might be missing, so guard before using it.

for a middle

Use @Optional plus isPresent/getOrNull and explain the named-no-match case.

for a senior

Tie absence to conditional registration patterns and choose isPresent vs orElse appropriately.

for a principal

Set conventions for which services are guaranteed vs optional across a plugin suite so tasks degrade predictably.

## The absence case is real It's tempting to treat `@ServiceReference` like dependency injection that always succeeds. It doesn't. Two situations leave the property empty: 1. A **named** reference (`@ServiceReference("metrics")`) where no service registered under `"metrics"`. 2. A **type** reference where no registered service matches the type. When empty, `property.get()` throws `MissingValueException` at execution. ## @Optional + isPresent Gradle validates task inputs/outputs. Without `@Optional`, an unset property can trip validation depending on how it's annotated. The clean pattern: ```kotlin abstract class ReportTask : DefaultTask() { @get:ServiceReference("metrics") @get:Optional abstract val metrics: Property<MetricsService> @TaskAction fun run() { val m = metrics.getOrNull() ?: run { logger.info("metrics service not registered; skipping") return } m.flush() } } ``` Use whichever fits: `isPresent` for a branch, `getOrNull()` for null-coalescing, `orElse(provider)` to supply a fallback. ## Why conditional registration happens Plugins commonly register a service only under a condition — e.g. inside `pluginManager.withPlugin("some.other") { ... registerIfAbsent(...) }`. Tasks that opportunistically integrate with that service must tolerate its absence so they don't break builds where the other plugin isn't applied. ## Contrast with required services If a service is *always* registered by the same plugin that adds the task, you can rely on it — but defensive coding still documents intent and survives refactors that make the service conditional later.

  • What exception is thrown if you call .get() on an unmatched @ServiceReference?
    A MissingValueException at execution time, because the property has no value to return.
  • Give a realistic reason a referenced service might not be registered.
    It's registered conditionally — e.g. only inside pluginManager.withPlugin(...) when another plugin is applied — so builds without that plugin never register it.

saying these in an interview costs you the question

  • Assuming @ServiceReference always injects a value like constructor injection.
  • Calling .get() unconditionally on a named reference without @Optional or a presence check.

context