How do you publish a component so that two of its optional features are mutually exclusive via capabilities, and what's the difference between feature variants and a capability conflict?
answer
- registerFeature => capability group:project-feature
- outgoing { capability(...) } on consumable config
- requireCapability = opt into a feature
- same capability on two modules = exclusivity
- feature = additive; conflict = mutual exclusion
basics
~20 sWhen publishing, declare a capability on a variant via the Java library's registerFeature or by adding capabilities to outgoing configurations. If you give two alternative implementations the same capability, consumers can pick only one — Gradle enforces mutual exclusivity at resolution.
solid answer
~40 sOn the **producer** side you attach capabilities to outgoing (consumable) configurations. The Java ecosystem exposes `java { registerFeature("name") { usingSourceSet(...) } }`, which creates apiElements/runtimeElements-style variants carrying a capability of `group:base-featureName`. For raw control you add a capability directly to a consumable configuration via `outgoing { capability("g:n:v") }`. To model two *alternative* implementations (say `netty` vs `nio` transports) as mutually exclusive, publish them as separate modules/variants that declare the **same** capability. Consumers requesting both then hit a capability conflict and must select one. Distinction: a **feature variant** is a normal, optional, *additive* capability a consumer opts into (`requireCapability`); a **capability conflict** arises only when two providers claim the *same* capability in one graph. Same machinery (capabilities), different intent — opt-in extras vs enforced exclusivity.
code
kotlin · 13 lines// PRODUCER: optional feature variant from a sibling source set
java {
registerFeature("nettyTransport") {
usingSourceSet(sourceSets["nettyTransport"])
}
}
// CONSUMER: opt into that feature variant
dependencies {
implementation("com.acme:lib:1.0") {
capabilities { requireCapability("com.acme:lib-nettyTransport") }
}
}go deeper
Aware that producers can attach capabilities and consumers can require them; deep detail optional.
Use registerFeature and requireCapability for an optional feature.
Add raw capabilities to outgoing variants and explain how shared capabilities enforce exclusivity.
Design published metadata so 'pick one of these alternatives' is enforced by capabilities as org governance, and document the coordinates.
## Producer-side capabilities A **consumable** configuration (one with `canBeConsumed = true`, `canBeResolved = false`) represents a variant other projects can depend on. Each such variant can declare capabilities via its `outgoing` container: ```kotlin configurations { val intransport by creating { isCanBeConsumed = true isCanBeResolved = false outgoing { capability("com.acme:transport:1.0") } } } ``` ## Feature variants (the Java way) For JVM libraries, the idiomatic approach is `registerFeature`: ```kotlin java { registerFeature("nettyTransport") { usingSourceSet(sourceSets["nettyTransport"]) } } ``` This creates `nettyTransportApiElements`/`nettyTransportRuntimeElements` variants, each carrying the capability `group:project-nettyTransport`. A consumer opts in: ```kotlin dependencies { implementation("com.acme:lib") { capabilities { requireCapability("com.acme:lib-nettyTransport") } } } ``` That is **additive**: you're asking for an optional feature. ## Modeling mutual exclusivity To make two implementations exclusive, give them the **same capability**. If `netty-transport` and `nio-transport` are separate modules both declaring `com.acme:transport`, then any graph pulling both gets a **capability conflict** — exactly the enforcement you want for "choose one transport." You publish each with: ```kotlin // in each transport module's build configurations.named("runtimeElements") { outgoing { capability("com.acme:transport:${version}") } } ``` Now the *consumer* must resolve it (capabilitiesResolution) — the producer has encoded the policy that only one transport is valid. ## Feature variant vs capability conflict | Aspect | Feature variant | Capability conflict | |---|---|---| | Intent | Optional add-on a consumer opts into | Two providers of one feature can't coexist | | Capability uniqueness | Distinct capability per feature | **Same** capability on two providers | | Consumer action | `requireCapability(...)` to opt in | `capabilitiesResolution { select(...) }` to choose | | Default if untouched | Feature simply not included | Hard build failure | Both ride on the **same capability machinery** — the difference is whether the capability is *unique* (opt-in extra) or *shared by alternatives* (enforced exclusivity). ## Design implications - Use feature variants to ship optional integrations from one codebase without separate artifacts. - Use shared capabilities across alternative modules to guarantee consumers don't accidentally combine incompatible implementations. - Document the capability coordinate so downstream teams know what to `requireCapability`/resolve. - This is a governance lever: a platform team can encode "these are alternatives, pick one" into published metadata rather than relying on docs.
- What capability does registerFeature("foo") create by default?A capability of the form group:projectName-foo, attached to the generated apiElements/runtimeElements variants for that feature.
- How does a consumer opt into a feature variant?On the dependency, add capabilities { requireCapability("group:name-feature") } so resolution selects the variant carrying that capability.
- How is a feature variant different from a capability conflict if both use capabilities?A feature variant uses a unique capability you opt into (additive); a conflict arises only when two providers share the same capability, making them mutually exclusive and failing by default.
saying these in an interview costs you the question
- Conflating registerFeature (opt-in optional feature) with capability conflicts (enforced exclusivity) — they share machinery but have opposite intent.
- Adding capabilities to resolvable configurations — capabilities are declared on consumable/outgoing variants.
- Assuming consumers automatically get feature variants — they must requireCapability to pull them in.