How do Gradle feature variants compare to Maven's <optional>true dependencies and to multiple Maven modules?
answer
- <optional>true = consumer re-declares everything
- multiple modules = release overhead
- feature variant = opt-in capability, transitive deps
- metadata-only; POM degrades to <optional>
- one artifact, one version
basics
~10 sMaven 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.
solid answer
~40 sMaven offers two patterns for optional integrations. **`<optional>true`** means the dependency is not transitive: the consumer who wants the optional feature must manually re-declare *every* optional dependency, with *no* version coordination from the producer. **Splitting into multiple modules** (e.g. `lib-core`, `lib-mongo`) works but multiplies artifacts, versions and release overhead. **Gradle feature variants** improve on both: one module declares optional functionality behind a **capability**; the consumer opts in once with `requireCapability(...)` and Gradle pulls in the feature's full, version-coordinated dependency set transitively. The trade-off: this richness lives in **Gradle Module Metadata**, so a pure-Maven consumer reading only the POM won't get the variant wiring (optional deps still render as `<optional>` in the POM for graceful degradation). So feature variants give Gradle consumers a better experience while staying POM-compatible at a reduced fidelity.
go deeper
Know feature variants are Gradle's answer to optional dependencies and are opt-in.
Contrast with <optional>true (manual re-declare) and explain the consumer requests a capability.
Discuss the metadata-vs-POM fidelity trade-off and graceful degradation to <optional>.
Frame the choice between one module with feature variants vs many modules as a release-governance and consumer-base (Gradle vs Maven) decision.
## The three approaches ### Maven `<optional>true` ```xml <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver-sync</artifactId> <version>4.11.0</version> <optional>true</optional> </dependency> ``` Meaning: "I compile against this, but I won't force it on you." A consumer who needs Mongo support must **manually re-declare** the driver — *and* know the right version, *and* re-declare any of its optional transitive deps. Brittle and undiscoverable. ### Multiple Maven modules Split into `lib-core` + `lib-mongo` where `lib-mongo` depends on `lib-core` and the driver. Clean transitively, but now you publish, version and release **N modules**, and consumers must know which add-on module to pick. Coordination overhead grows with the number of optional integrations. ### Gradle feature variants ```kotlin java { registerFeature("mongoSupport") { usingSourceSet(sourceSets["main"]) } } dependencies { "mongoSupportApi"("org.mongodb:mongodb-driver-sync:4.11.0") } ``` One module, one version. A consumer opts in: ```kotlin implementation("com.example:lib:1.0") { capabilities { requireCapability("com.example:lib-mongoSupport") } } ``` Gradle then adds the driver **and its transitive deps**, all at the version the producer tested. Discoverable (the metadata lists the variant) and version-coordinated. ## Interop with Maven consumers Feature variants are expressed in **Gradle Module Metadata** (`.module`). When you publish, Gradle also writes a POM. Optional feature dependencies appear in the POM as `<optional>true` so Maven builds still compile, but they **lose** the capability/opt-in wiring — Maven consumers fall back to the manual re-declare model. This is the deliberate graceful-degradation contract. ## When to choose which | Goal | Pick | |---|---| | Optional integration, Gradle consumers, one artifact | feature variant | | Hard module boundary / independent release cadence | separate modules | | Must serve Maven consumers with full fidelity | separate modules (capabilities won't carry) | ## Summary Feature variants are the modern Gradle answer: opt-in optional functionality with transitive, version-coordinated dependencies in a single module, degrading gracefully to `<optional>` for POM-only consumers.
- What does a Maven-only consumer see when you publish a module with feature variants?A normal POM where the feature's optional dependencies are marked <optional>true. They lose the capability/opt-in wiring and must re-declare what they need manually.
- When is splitting into separate modules still the better choice?When the parts need independent release cadence/versioning, when you must serve Maven consumers at full fidelity, or when the boundary is a genuine architectural module rather than an optional add-on.
saying these in an interview costs you the question
- Claiming feature variants give full-fidelity behavior to pure-Maven consumers.
- Saying Maven <optional> dependencies are transitive — they are not.