How do you define and register a BuildService with parameters, and how are those parameters supplied?
answer
- Params extends BuildServiceParameters
- managed interface of Property
- parameters field at runtime
- BuildServiceParameters.None
- registerIfAbsent idempotent by name
basics
~10 sDefine a nested Params interface extending BuildServiceParameters with Property fields, then register with registerIfAbsent and set those Property values inside the configuration action.
solid answer
~30 sA parameterized service implements `BuildService<P>` where `P extends BuildServiceParameters` — typically a nested interface of `Property`/`Provider`-typed members (a managed type, so Gradle implements it). At runtime the service reads them via the `parameters` field. You register with `gradle.sharedServices.registerIfAbsent("name", MyService::class) { parameters.someProp.set(value) }`; the configuration action's receiver exposes `parameters` and the spec (`maxParallelUsages`). Because params are wired through lazy `Provider`s, values can be deferred and are serialized by the configuration cache, keeping the service config-cache compatible. If a service needs no params, use the built-in `BuildServiceParameters.None`.
code
kotlin · 10 linesabstract class GreetService : BuildService<GreetService.Params> {
interface Params : BuildServiceParameters {
val greeting: Property<String>
}
fun greet(name: String) = parameters.greeting.get() + ", " + name
}
val svc = gradle.sharedServices.registerIfAbsent("greet", GreetService::class) {
parameters.greeting.set("Hello")
}go deeper
Knows params are a nested interface with Property fields set during registration.
Explains managed types, BuildServiceParameters.None, and reading via the parameters field.
Connects Provider-based params to configuration-cache serialization and idempotent registration.
Sets conventions: keep params declarative/serializable, build live resources internally, standardize service naming to avoid collisions across plugins.
## Parameters are a managed type A BuildService receives its configuration through a **parameters** object whose type extends `BuildServiceParameters`. You usually declare it as a **nested interface** with abstract `Property<T>` / `ListProperty<T>` / `Provider<T>` accessors. Gradle generates the implementation (a 'managed type'), so you never write a constructor or backing fields for params. ```kotlin abstract class HttpClientService : BuildService<HttpClientService.Params>, AutoCloseable { interface Params : BuildServiceParameters { val baseUrl: Property<String> val timeoutMillis: Property<Long> } private val client = buildClient( parameters.baseUrl.get(), parameters.timeoutMillis.getOrElse(30_000) ) fun get(path: String): String = client.get(path) override fun close() = client.close() } ``` The abstract `parameters` property is injected by Gradle and returns the typed params object. ## Registration supplies the values ```kotlin val http = gradle.sharedServices.registerIfAbsent("http", HttpClientService::class) { parameters.baseUrl.set("https://example.com") parameters.timeoutMillis.set(10_000) maxParallelUsages.set(4) } ``` The lambda's receiver is a `BuildServiceSpec<Params>` exposing: - **`parameters`** — set the managed property values (lazily; you can pass other `Provider`s). - **`maxParallelUsages`** — cap concurrency (see the dedicated question). ## No-parameter services When a service needs no configuration, parameterize it with the built-in **`BuildServiceParameters.None`**: ```kotlin abstract class Counter : BuildService<BuildServiceParameters.None> { /* ... */ } ``` ## Why Provider-based params matter Because params are `Property`/`Provider` values, they participate in lazy evaluation and are **serialized by the configuration cache**. This is what lets a service survive a cached configuration phase: the params snapshot is stored and replayed, and the service is reconstructed on cache hit. Putting non-serializable mutable objects directly into params defeats this — keep params to serializable, declarative inputs and build live resources inside the service body. ## `registerIfAbsent` semantics The first argument is a **name** acting as a registration key. Calling `registerIfAbsent` again with the same name returns the existing `Provider` and does **not** re-run the config action — this makes registration idempotent across plugins that might both want the service.
- What happens if two plugins both call registerIfAbsent with the same name?The second call returns the already-registered Provider and skips the config action — registration is idempotent by name, so the service is shared, not duplicated.
- Why should params be Property/Provider types rather than plain fields?So they're lazy and serializable by the configuration cache, letting the service's configuration be cached and replayed.
saying these in an interview costs you the question
- Writing a constructor to receive params — params come from the managed parameters field, not constructor args.
- Stuffing live, non-serializable objects (sockets, threads) into params instead of constructing them inside the service.