skip to content

Capabilities and Feature Variants

Declaring capabilities and registering optional feature variants backed by separate source sets. Interviewers ask because it is Gradle's structured answer to optional dependencies, a problem Maven never solved.

on this pageshow

questions

5

How do you publish an optional feature variant with the java-library plugin using registerFeature, and what does usingSourceSet do?

level: middleimportance: must knowfreq 40%

answer

  1. java { registerFeature(name){ usingSourceSet(...) } }
  2. creates nameApi / nameImplementation
  3. capability = base + '-feature'
  4. main source set vs dedicated source set
  5. consumer: requireCapability

basics

~10 s

In the java block call registerFeature("name") { usingSourceSet(...) }. It creates nameApi/nameImplementation configurations and an optional variant with capability group:base-feature:version that consumers opt into via requireCapabilities.

solid answer

~40 s

A **feature variant** lets one module ship optional functionality behind a distinct capability. With the `java-library` (or `java`) plugin you call `registerFeature` inside the `java {}` block: ```kotlin java { registerFeature("mongoSupport") { usingSourceSet(sourceSets["main"]) } } ``` This registers `mongoSupportApi` and `mongoSupportImplementation` dependency buckets and creates new outgoing variants (`mongoSupportApiElements`, `mongoSupportRuntimeElements`) carrying the capability `group:project-mongoSupport:version`. The feature's dependencies stay **optional** — a plain consumer doesn't get them. To pull them in, the consumer requests the capability via `requireCapabilities`/`requireCapability`. `usingSourceSet(main)` says the feature's classes live in the main source set; passing a **dedicated** source set instead keeps the feature's code physically separate and avoids it being published twice. Feature variants require Gradle Module Metadata to be honoured by consumers.

code

kotlin · 19 lines
kotlin
// Producer: optional Mongo support as a feature variant
val mongoSupport = sourceSets.create("mongoSupport")

java {
    registerFeature("mongoSupport") {
        usingSourceSet(mongoSupport)
    }
}

dependencies {
    "mongoSupportImplementation"("org.mongodb:mongodb-driver-sync:4.11.0")
}

// Consumer opts in by requesting the capability
dependencies {
    implementation("com.example:json-lib:1.0") {
        capabilities { requireCapability("com.example:json-lib-mongoSupport") }
    }
}

go deeper

for a junior

Know that registerFeature creates an optional variant and that consumers must opt in.

for a middle

Show the registerFeature/usingSourceSet syntax, the generated configurations, and how a consumer requests the capability.

for a senior

Contrast main vs dedicated source set, explain the generated capability naming, and how it publishes via Module Metadata.

for a principal

Position feature variants as a way to ship one artifact with optional integrations instead of N coupling modules, and weigh the Maven-consumer interop cost.

## The problem feature variants solve A library sometimes has *optional* functionality: e.g. a JSON library with optional Mongo support that only some consumers need, pulling in an optional `mongodb-driver` dependency. You don't want every consumer to drag in that driver. Maven's answer is `<optional>true` (consumer must re-declare it). Gradle's answer is a **feature variant**: an opt-in variant identified by its own **capability**. ## Declaring a feature With the `java`/`java-library` plugin: ```kotlin java { registerFeature("mongoSupport") { usingSourceSet(sourceSets["main"]) } } dependencies { "mongoSupportImplementation"("org.mongodb:mongodb-driver-sync:4.11.0") } ``` `registerFeature("mongoSupport")` does several things: - creates dependency buckets `mongoSupportApi` and `mongoSupportImplementation`; - creates **consumable variants** `mongoSupportApiElements` and `mongoSupportRuntimeElements`; - gives those variants a capability `${group}:${name}-mongoSupport:${version}` (the base capability with `-featureName` appended). ## What usingSourceSet controls `usingSourceSet(sourceSet)` declares **which source set produces the feature's classes**. - `usingSourceSet(sourceSets["main"])` — the feature shares the main classes. Use when the optional code is already compiled into the main jar and only the *dependencies* are optional. Note: a source set can back only one feature this way, and using `main` means no separate artifact. - A **dedicated** source set (e.g. `sourceSets.create("mongoSupport")`) — the feature has its **own** classes, compiled separately and published as a **separate jar** with a classifier. This is the cleaner pattern when the optional code is genuinely separate. ```kotlin val mongoSupport = sourceSets.create("mongoSupport") java { registerFeature("mongoSupport") { usingSourceSet(mongoSupport) } } ``` ## How a consumer opts in The consumer requests the capability: ```kotlin dependencies { implementation("com.example:json-lib:1.0") { capabilities { requireCapability("com.example:json-lib-mongoSupport") } } } ``` Without that request the feature's `mongodb-driver` dependency is **not** added. ## Publishing When you publish with `maven-publish` and the `java` component, feature variants are included automatically in **Gradle Module Metadata**. Maven-only consumers (reading the POM) will see the dedicated-source-set artifact only as a classified jar without the optional-dependency wiring — feature variants are fully expressed only through `.module` metadata. ## Key APIs - `java { registerFeature(name) { usingSourceSet(ss) } }` - generated buckets: `${name}Api`, `${name}Implementation` - capability: `group:base-name:version` with `-feature` suffix - consumer: `requireCapability(...)`

  • What capability name is generated for a feature called 'mongoSupport' in module com.example:json-lib?
    `com.example:json-lib-mongoSupport:<version>` — the base GAV with the feature name appended to the artifact name.
  • When would you use a dedicated source set instead of usingSourceSet(main)?
    When the optional code is genuinely separate and you want it compiled and published as its own classified jar, so consumers who don't opt in never load those classes — not just skip the dependency.
  • Does a normal consumer get the feature's dependencies automatically?
    No. Feature dependencies are pulled in only when the consumer explicitly requests the feature's capability via requireCapability.

saying these in an interview costs you the question

  • Saying registerFeature lives in the dependencies block — it's inside the java {} block.
  • Claiming the feature's optional dependencies are added to every consumer by default.

context

open as a page

What is a capability in Gradle, and how does it differ from a module's GAV coordinates?

level: middleimportance: must knowfreq 45%

basics

~10 s

A capability is a logical 'what this provides' identifier (group:name:version) attached to a variant. Two modules offering the same capability conflict, so only one can be on the classpath.

open as a page

What capability does the java-test-fixtures plugin create, and how does a consumer depend on another project's test fixtures?

level: middleimportance: should knowfreq 30%

basics

~10 s

The java-test-fixtures plugin adds a testFixtures source set and publishes a variant with capability group:name-test-fixtures. Consumers depend on it via testImplementation(testFixtures(project(":mod"))).

open as a page

Two dependencies in your graph provide the same capability. What happens, and how do you resolve the conflict?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Gradle fails resolution with a capability conflict. You resolve it with resolutionStrategy.capabilitiesResolution.withCapability("g:n") { select(...) }, or by excluding/replacing one module so only one variant provides the capability.

open as a page

How do Gradle feature variants compare to Maven's <optional>true dependencies and to multiple Maven modules?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Maven optional deps force the consumer to re-declare everything by hand with no version guidance. Feature variants attach optional deps to an opt-in capability, so requesting the capability pulls the right transitive deps automatically.

open as a page