skip to content

What is the BuildFeatures service in Gradle, and how does a plugin obtain it to learn whether the configuration cache is in play?

level: middleimportance: must knowfreq 45%

answer

  1. serviceOf<BuildFeatures>()
  2. configurationCache.requested vs active
  3. Provider<Boolean>
  4. @Inject into plugin/task
  5. since Gradle 8.5

basics

~10 s

BuildFeatures is a Gradle service exposing whether opt-in features like the configuration cache are requested and active. A plugin gets it via gradle.serviceOf<BuildFeatures>() (or constructor injection) and reads configurationCache.requested / .active.

solid answer

~40 s

`BuildFeatures` is a built-in Gradle service (since 8.5) that lets plugin and build-logic code introspect the state of opt-in build features without scraping system properties or command-line args. The most common consumer is the configuration cache: `buildFeatures.configurationCache` exposes two `Provider`s — `requested` (the user asked for it, possibly disabled) and `active` (it is genuinely in effect for this invocation). You obtain the service either by `@Inject`-ing `BuildFeatures` into a plugin/task constructor or, from build scripts/`Settings`/`Gradle`, via `gradle.serviceOf<BuildFeatures>()`. Because the values are `Provider<Boolean>`, you read them lazily at execution time with `.get()` (or `.getOrElse`), which is itself configuration-cache-safe. Typical use: conditionally skip an API that is incompatible with the cache, or emit a warning/telemetry about the active mode.

code

kotlin · 11 lines
kotlin
import org.gradle.api.configuration.BuildFeatures
import org.gradle.kotlin.dsl.serviceOf

class DiagnosticsPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val features = project.gradle.serviceOf<BuildFeatures>()
        val requested = features.configurationCache.requested.getOrElse(false)
        val active = features.configurationCache.active.getOrElse(false)
        project.logger.lifecycle("config-cache requested=$requested active=$active")
    }
}

go deeper

for a junior

Know that BuildFeatures tells you if the configuration cache is on, obtained via gradle.serviceOf<BuildFeatures>().

for a middle

Explain requested vs active, the Provider<Boolean> shape, and both injection and serviceOf access paths.

for a senior

Discuss why providers keep the read cache-safe and when you'd branch plugin behaviour on active vs requested.

for a principal

Frame it as the supported replacement for brittle property-sniffing and a foundation for org-wide build telemetry and feature governance.

## What problem BuildFeatures solves Before Gradle 8.5, build logic that wanted to know "is the configuration cache on right now?" had to read internal system properties (`org.gradle.configuration-cache`) or sniff the start parameters — brittle, undocumented, and easy to get wrong (requested vs. effectively active are different things). `BuildFeatures` is the supported, stable API for this introspection. ## The service shape ``` interface BuildFeatures { val configurationCache: ConfigurationCacheFeature val isolatedProjects: IsolatedProjectsFeature } interface BuildFeatureValue { val requested: Provider<Boolean> // user asked for it (may be null/absent if never specified) val active: Provider<Boolean> // it is actually in effect for this invocation } ``` Each feature exposes two **`Provider<Boolean>`** values: - **`requested`** — did the user opt in (via `--configuration-cache`, `org.gradle.configuration-cache=true` in `gradle.properties`, etc.)? It can be *absent* (the provider has no value) when nothing was specified, so prefer `.getOrElse(false)`. - **`active`** — is the feature genuinely operating for *this* build invocation? A feature can be requested but inactive (e.g. disabled by a fatal problem, or by `--no-configuration-cache` overriding properties). ## Obtaining the service Two idiomatic paths: 1. **Injection** (preferred in plugins/tasks): declare `BuildFeatures` as a constructor parameter and let Gradle's services inject it. This is the cleanest and works in custom tasks. 2. **`serviceOf`**: from a `Project`, `Settings`, or `Gradle` object you can call `gradle.serviceOf<BuildFeatures>()` (the `org.gradle.kotlin.dsl.serviceOf` extension). Useful in settings/init scripts. ```kotlin import org.gradle.api.configuration.BuildFeatures import org.gradle.kotlin.dsl.serviceOf val features = gradle.serviceOf<BuildFeatures>() val ccActive = features.configurationCache.active.get() ``` ## Why the values are Providers Returning `Provider<Boolean>` rather than a plain `Boolean` keeps the read **lazy** and configuration-cache-compatible: you can wire the provider into a task input or read it at execution time without forcing premature evaluation during configuration. Reading `BuildFeatures` providers does not break the cache. ## Typical consumers - A plugin that uses a configuration-cache-incompatible API only when the cache is **not** active, and switches to a compatible path when it is. - Telemetry/diagnostics that log which mode a CI build ran in. - Gradual migration: warn (don't fail) when `requested` is true but `active` is false, signalling that some problem demoted the build.

  • Why does Gradle return Provider<Boolean> instead of a plain Boolean here?
    To keep the read lazy and configuration-cache-safe — you can wire it into task inputs or evaluate it at execution time without forcing eager evaluation during configuration.
  • What Gradle version introduced BuildFeatures?
    Gradle 8.5. Before that you had to inspect internal start-parameter/system-property state, which was unsupported.

saying these in an interview costs you the question

  • Claiming BuildFeatures returns plain Booleans (they are Providers).
  • Saying requested and active are the same thing — they differ when a feature is requested but demoted/overridden.
  • Reading internal system properties like org.gradle.configuration-cache instead of the supported service.

context