Walk through registering a BuildService with gradle.sharedServices.registerIfAbsent and passing parameters. Why is registerIfAbsent preferred over a plain register, and what does it return?
answer
- gradle.sharedServices.registerIfAbsent(name, Type){}
- returns Provider<MyService>, lazy
- idempotent → reuse across plugins
- Params extends BuildServiceParameters, serializable
- Params.None when no config
basics
~10 sCall gradle.sharedServices.registerIfAbsent("name", MyService::class) { parameters { ... } }. It returns a Provider<MyService>. registerIfAbsent is idempotent, so registering the same name twice (e.g. from two plugins) reuses one instance instead of failing.
solid answer
~40 sYou register against `gradle.sharedServices`: `registerIfAbsent(name, MyServiceType) { parameters { x.set(...) } }`. The service type is an abstract class extending `BuildService<P>` where `P` extends `BuildServiceParameters`; Gradle implements the abstract `getParameters()`. The call returns a **`Provider<MyService>`** — lazy, so the service isn't created until a task consumes it. **`registerIfAbsent`** is preferred because registration is keyed by name: if another plugin (or your own code running twice) registers the same name, you get the **already-registered instance** instead of a duplicate-registration error. Parameters are how you pass immutable configuration into the service (paths, flags, a `RegularFileProperty` output) — they must themselves be config-cache serializable. The returned provider is what you wire into each task's `Property<MyService>`.
code
kotlin · 14 linesinterface DownloadParams : BuildServiceParameters {
val server: Property<String>
}
abstract class DownloadService : BuildService<DownloadParams>, AutoCloseable {
private val client = java.net.http.HttpClient.newHttpClient()
fun base() = parameters.server.get()
override fun close() {}
}
val dl = gradle.sharedServices.registerIfAbsent("download", DownloadService::class) {
parameters { server.set("https://repo.example.com") }
maxParallelUsages.set(2)
}go deeper
Recall the call shape: registerIfAbsent(name, Type){ parameters{} } returning a Provider.
Explain idempotency, laziness, the abstract service + Params pattern, and serializable params.
Cover maxParallelUsages throttling and cross-plugin sharing semantics keyed by name.
Define naming conventions so independent plugins converge on one shared service rather than fragmenting state.
## The registration API Build services are registered through the **`BuildServiceRegistry`** exposed as `project.gradle.sharedServices`. The primary entry point is: ```kotlin val provider: Provider<MyService> = gradle.sharedServices.registerIfAbsent("myService", MyService::class) { parameters { // configure the service's Params object } maxParallelUsages.set(1) // optional concurrency limit } ``` ### The service type and its Params A build service is an **abstract class** implementing `BuildService<P>`: ```kotlin interface MyParams : BuildServiceParameters { val rootDir: DirectoryProperty val verbose: Property<Boolean> } abstract class MyService : BuildService<MyParams>, AutoCloseable { // getParameters() is implemented by Gradle override fun close() { /* release resources */ } } ``` Gradle generates the implementation of `getParameters()`, so inside the service you read configuration via `parameters.rootDir.get()`. If the service needs no parameters, use `BuildServiceParameters.None`. ### Why `registerIfAbsent` over `register` Registration is **keyed by the name string**. The older `register(...)` fails if the name is already taken. `registerIfAbsent`: - **Is idempotent** — calling it again with the same name returns the *existing* registration's provider. This matters because plugins are applied independently; two plugins both wanting a shared HTTP client or a shared counter can each call `registerIfAbsent("http", ...)` and end up sharing **one** instance. - Avoids accidental duplicate-registration failures in multi-plugin or multi-subproject builds. ### What it returns and laziness It returns a **`Provider<MyService>`**. The provider is lazy: the service is **not instantiated at registration time**. Gradle creates the single instance the first time a task actually calls `provider.get()` (or the service is injected). This laziness is what keeps registration cheap and config-cache friendly. ### Parameters must be serializable Whatever you put in the Params object is captured as part of the configuration cache, so it must be a config-cache-supported type — Gradle managed properties (`Property`, `RegularFileProperty`, `DirectoryProperty`, `ListProperty`, etc.) are ideal because they're lazy and serializable. Don't stuff live `Project` or `Task` references into Params. ### Optional knobs - `maxParallelUsages` limits how many tasks may use the service concurrently — useful to throttle access to a constrained external resource. - `parameters { }` is omitted (or empty) for `BuildServiceParameters.None`.
- What type does registerIfAbsent return and when is the service actually created?It returns a Provider<MyService>. The instance is created lazily the first time a task consumes the provider (calls get() or has it injected), not at registration time.
- Two plugins both call registerIfAbsent with name 'http'. What happens?They share a single instance — the second call returns the provider for the already-registered service rather than failing, which is the main reason to prefer registerIfAbsent over register.
saying these in an interview costs you the question
- Saying registration eagerly creates the service — it's lazy via the Provider.
- Putting live Project/Task references into the Params object, which breaks config-cache serialization.