skip to content

BuildService for Shared State

BuildService as long-lived, parallel-safe shared state registered on the build and closed automatically when it ends. Interviewers ask it as the modern replacement for static fields and mutable project state.

on this pageshow

questions

5

How does a BuildService perform cleanup at the end of a build, and what makes this a lifecycle hook?

level: middleimportance: must knowfreq 45%

answer

  1. implement AutoCloseable
  2. close() called once at build end
  3. runs only if instantiated (lazy)
  4. Gradle owns the call — never close() yourself
  5. config-cache-safe replacement for buildFinished

basics

~10 s

Make the service implement AutoCloseable. Gradle calls close() automatically when the build finishes, so you can stop a server or flush a buffer there. You never call close() yourself.

solid answer

~50 s

If a `BuildService` implements `AutoCloseable`, Gradle invokes its `close()` method **once, at the end of the build**, after all tasks have run. This is what makes the service act as a lifecycle hook: it gives you a guaranteed build-completion teardown point tied to a shared resource. Typical uses are stopping a started process (an embedded server, a Docker container), closing a connection pool, flushing buffered output, or printing an aggregated summary of what happened during the build. Gradle owns the call — you must not invoke `close()` manually, and you should make it idempotent and exception-safe so a teardown failure does not mask the real build result. Because the service is created lazily, `close()` only runs if the service was actually instantiated (i.e. at least one task used it). This is a cleaner replacement for `gradle.buildFinished {}`, which is deprecated/discouraged and incompatible with the configuration cache.

code

kotlin · 14 lines
kotlin
abstract class ServerService :
    BuildService<BuildServiceParameters.None>, AutoCloseable {

    private val server = startEmbeddedServer()
    fun baseUrl(): String = server.url

    override fun close() {
        // Gradle calls this once, after all tasks finish
        server.stop()
    }
}

val server = gradle.sharedServices
    .registerIfAbsent("server", ServerService::class.java) {}

go deeper

for a junior

Know that implementing AutoCloseable lets Gradle clean up the service automatically at build end.

for a middle

Explain the guarantees: once, after all tasks, only if instantiated, owned by Gradle; give a concrete teardown use.

for a senior

Contrast with deprecated buildFinished and the configuration-cache angle; discuss idempotent/exception-safe close().

for a principal

Position it as the standard build-completion teardown pattern for shared infrastructure across the org's plugins, eliminating cache-hostile callbacks.

## The lifecycle-hook angle A `BuildService` is interesting as a **lifecycle hook** because it offers a sanctioned place to run code at **build completion** that is bound to a shared resource. The mechanism is the standard Java `AutoCloseable` contract: ```kotlin abstract class ServerService : BuildService<BuildServiceParameters.None>, AutoCloseable { private val server = startEmbeddedServer() fun baseUrl() = server.url override fun close() { server.stop() // runs once, at end of build } } ``` When the build ends, Gradle disposes every service it created and calls `close()` on those that implement `AutoCloseable`. Crucially: - It runs **after all tasks finish**, regardless of which tasks used the service. - It runs **only if the service was instantiated** — lazy creation means an unused registration is never built and never closed. - It runs **once**; Gradle manages the single instance. ## Why prefer this over buildFinished The old `gradle.buildFinished { }` callback is discouraged and is **not compatible with the configuration cache** because it captures build-script state. A `BuildService` that holds the resource and closes itself is the modern, cache-friendly equivalent: the teardown lives with the resource it tears down, and Gradle owns the timing. ## Writing a safe close() Because `close()` runs during build teardown, follow defensive rules: 1. **Idempotent** — guard against double-close even though Gradle calls it once. 2. **Exception-safe** — wrap risky shutdown so a cleanup error is logged, not allowed to obscure the actual build outcome. 3. **No new task work** — it is teardown, not a place to start fresh build logic. ## Common real uses - Stop an embedded web server / test fixture started for integration tests. - Close a shared HTTP client, DB connection pool, or browser/WebDriver. - Flush and write an aggregated report (e.g. total bytes processed across tasks). - Tear down ephemeral infrastructure (containers) provisioned for the build.

  • If no task ever uses the service, does close() run?
    No. The service is created lazily, so an uninstantiated service is never closed.
  • Why is a self-closing BuildService preferred over gradle.buildFinished {}?
    buildFinished is discouraged and breaks the configuration cache because it captures build-script state; the service's close() is owned by Gradle and is cache-compatible.
  • Should you call close() yourself from a task's doLast?
    No. Gradle manages the single instance and calls close() at build end; calling it manually would tear the resource down while other tasks may still need it.

saying these in an interview costs you the question

  • Calling close() manually from task code.
  • Assuming close() runs even when the service was never instantiated.
  • Putting real build logic in close() instead of pure teardown.

context

open as a page

What is a Gradle BuildService, and why would you use one to hold state that is shared across tasks during a build?

level: middleimportance: must knowfreq 55%

basics

~20 s

A BuildService is an object Gradle creates lazily and shares across tasks in a build. It holds state (a counter, a connection pool) safely, lives for the whole build, and Gradle closes it at build end.

open as a page

What are the semantics of gradle.sharedServices.registerIfAbsent, and why is it the recommended way to register a BuildService from a plugin?

level: middleimportance: should knowfreq 28%

basics

~20 s

registerIfAbsent registers a named build service if one with that name doesn't already exist, and returns its Provider either way. That makes it idempotent, so multiple plugins can register the same service safely without conflicts.

open as a page

What does Task.usesService(provider) do, and why is it needed when a task relies on a BuildService for shared state?

level: seniorimportance: should knowfreq 35%

basics

~10 s

usesService tells Gradle a task depends on that service. It keeps the service alive while the task runs and lets Gradle order/limit access correctly. Without it, parallel cleanup or constraints can misbehave.

open as a page

A plugin author uses a static field / Project extension to share a counter across tasks and it breaks under --parallel and the configuration cache. Why, and how does a BuildService fix it?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Static/global state isn't safe across parallel tasks or worker processes and the configuration cache forbids capturing live build-script objects. A BuildService is a Gradle-managed, build-scoped, shareable holder that works under both.

open as a page