What is a Gradle BuildService, and what problem does it solve?
answer
- BuildService<Params>
- sharedServices.registerIfAbsent
- Provider<Service>, lazy
- config-cache safe shared state
- AutoCloseable cleanup
basics
~10 sA BuildService is a shared, build-scoped object that holds state or resources (caches, connections) and can be safely shared across tasks, including tasks running in parallel.
solid answer
~30 sA `BuildService` is the configuration-cache-compatible way to hold shared mutable state or expensive resources during a build. You implement `BuildService<Params>` (where `Params extends BuildServiceParameters`) and register it lazily via `gradle.sharedServices.registerIfAbsent(name, Type) { ... }`, which returns a `Provider<MyService>`. Tasks declare they use it through a `@ServiceReference` property or `usesService(provider)`, and Gradle injects a single shared instance, created lazily on first use. The service replaces the old anti-pattern of static fields / project state, which break with the configuration cache and parallel execution. It can implement `AutoCloseable` so Gradle closes it at build end, and can cap concurrent access with `getMaxParallelUsages()`.
code
kotlin · 12 linesabstract class WebServer : BuildService<BuildServiceParameters.None>, AutoCloseable {
private val server = startServer()
val port: Int get() = server.port
override fun close() = server.stop()
}
val serverProvider = gradle.sharedServices.registerIfAbsent("webServer", WebServer::class) { }
tasks.register("integTest") {
usesService(serverProvider)
doLast { println("server on " + serverProvider.get().port) }
}go deeper
Knows it's a shared object usable by multiple tasks; can name registerIfAbsent.
Explains Params, lazy Provider, usesService, and why static state breaks under config cache.
Discusses lifecycle (lazy init + AutoCloseable close), parallel-execution safety, and Worker API integration.
Frames BuildService as the org-wide pattern for shared resources, sets conventions for thread-safety and resource governance across in-house plugins.
## What a BuildService is A **BuildService** is an object whose lifecycle Gradle manages for a single build invocation. It is the official, configuration-cache-safe mechanism for **sharing state or expensive resources between tasks** — for example an in-memory cache, a database/HTTP connection pool, a counter, or a handle to an external process. Before build services, plugin authors stashed shared state in static fields, extension objects, or the `Project` instance. All of those break under two modern Gradle features: - **Configuration cache** — the task graph is serialized; holding references to `Project` or arbitrary mutable singletons is forbidden. - **Parallel task execution** — multiple tasks (and worker actions) may touch the same state concurrently. ## Anatomy You implement the abstract class `BuildService<P extends BuildServiceParameters>`: ```kotlin abstract class CounterService : BuildService<CounterService.Params> { interface Params : BuildServiceParameters { val initial: Property<Int> } private val count = AtomicInteger(parameters.initial.get()) fun next(): Int = count.incrementAndGet() } ``` - **`Params`** is a managed type (interface or abstract class) built from `Property`/`Provider` — it carries immutable configuration into the service and is itself serialized by the configuration cache. - The **`parameters`** field gives the service access to its params at runtime. - A no-arg constructor is required (Gradle instantiates it; you can inject services like `ProjectLayout` via `@Inject`). ## Registration ```kotlin val counter = gradle.sharedServices.registerIfAbsent("counter", CounterService::class) { parameters.initial.set(0) maxParallelUsages.set(1) } ``` `registerIfAbsent` returns a `Provider<CounterService>`. The name is a key: registering the same name twice returns the existing registration. The service is **created lazily** — only when a task that uses it actually executes. ## Consuming it from a task Two ways: 1. **`@ServiceReference`** (Gradle 8+) — declare an abstract `Property<CounterService>` annotated `@ServiceReference("counter")`; Gradle wires it and registers the usage automatically. 2. **Explicit** — hold a `Property<CounterService>`, set it from the provider, and call `usesService(counterProvider)` so Gradle knows the dependency (important for `maxParallelUsages` accounting). ## Lifecycle and cleanup If the service implements **`AutoCloseable`**, Gradle calls `close()` once at the end of the build — the right place to flush caches, close connections, or stop a process you started. ## Why it matters BuildService is the canonical answer to 'how do I share state across tasks safely?' in modern Gradle. It is compatible with the configuration cache, integrates with parallel execution and the Worker API, and gives you deterministic startup (lazy) and shutdown (`close()`).
- Why can't you just use a static field or a Project property for shared state anymore?Static fields aren't serialized/restored by the configuration cache and aren't safe under parallel execution; holding a Project reference is explicitly forbidden by the configuration cache. BuildService is the supported replacement.
- When is the service instance actually created?Lazily — on first access (e.g., when the first task that declares it via usesService/@ServiceReference executes), not at registration time.
saying these in an interview costs you the question
- Saying the service is created eagerly at registration — registration returns a Provider; instantiation is lazy.
- Claiming BuildService is just for parallelism limiting — its primary purpose is config-cache-safe shared state/resources.