What is the attributesSchema, and what is the difference between a compatibility rule and a disambiguation rule?
answer
- dependencies.attributesSchema
- compatibility = filter (acceptable?)
- disambiguation = chooser (best?)
- no matching vs ambiguous variant
- TargetJvmVersion: lower compatible, prefer highest
basics
~20 sThe attributesSchema declares each attribute and how Gradle compares values. A compatibility rule decides whether a producer value is acceptable for what the consumer asked. A disambiguation rule picks the best one when several are acceptable.
solid answer
~40 s`attributesSchema` is a project-level registry, accessible via `dependencies.attributesSchema`, that describes how every attribute participates in variant selection. For each attribute you register, you can attach two kinds of rules. A **compatibility rule** answers: given the consumer requested value C and a producer offers value P, is P *acceptable*? If P equals C it's trivially compatible; otherwise the rule may still mark it compatible (e.g. a JVM-8 variant is compatible with a consumer on JVM 17). A **disambiguation rule** runs *after* compatibility filtering: if multiple variants survive, it chooses the best candidate (e.g. prefer the highest `TargetJvmVersion`). Selection fails if zero variants are compatible (*no matching variant*) or if more than one survives disambiguation (*ambiguous variant*). Standard attributes like Usage and TargetJvmVersion ship with built-in rules; you supply custom rules for custom attributes.
code
kotlin · 16 linesval flavor = Attribute.of("com.acme.flavor", String::class.java)
dependencies.attributesSchema {
attribute(flavor) {
compatibilityRules.add(FlavorCompatRule::class.java)
disambiguationRules.add(FlavorDisambigRule::class.java)
}
}
abstract class FlavorCompatRule : AttributeCompatibilityRule<String> {
override fun execute(details: CompatibilityCheckDetails<String>) {
if (details.consumerValue == "free" && details.producerValue == "paid") {
details.compatible()
}
}
}go deeper
Likely out of scope; at most know Gradle has rules deciding which variant fits.
State that the schema defines per-attribute matching and that some attributes (JVM version) match non-exactly.
Cleanly separate compatibility (filter) from disambiguation (tie-break), name the failure modes, and read selection error messages.
Discuss governing custom attributes org-wide, the risk of brittle custom rules, and when to lean on built-ins versus introducing new dimensions.
## The schema's job Variant selection needs to compare attribute values that are not always equal. The **`attributesSchema`** is where Gradle learns *how* to compare each attribute. You reach it through `dependencies.attributesSchema { ... }`. Registering an attribute there lets you attach rule logic; attributes used without registration only match on exact equality. ## Two phases of selection Selection for one dependency edge proceeds in two phases per attribute: ### 1. Compatibility (a filter) For a requested consumer value and each candidate producer value, Gradle asks the **compatibility rule**: *is this candidate acceptable?* The rule's `CompatibilityCheckDetails` lets you call `compatible()` or `incompatible()`. Equal values are compatible by default. The classic example is `TargetJvmVersion`: requesting JVM 17, a variant built for JVM 8 is marked **compatible** because newer JVMs run older bytecode. After this phase you have the set of *acceptable* variants. ### 2. Disambiguation (a chooser) If more than one variant is still acceptable, the **disambiguation rule** picks the best via `MultipleCandidatesDetails.closestMatch(value)`. For `TargetJvmVersion` the built-in rule prefers the **highest** compatible version, so a consumer on JVM 17 with variants for 8 and 17 gets the 17 variant. If no disambiguation rule narrows it to exactly one, resolution fails with an **ambiguous variant** error listing the tied candidates. ## Failure modes - **No matching variant**: zero variants compatible for some attribute. Often means a consumer requested something the producer never published (wrong Usage, unsupported JVM). - **Ambiguous variant**: multiple equally-good variants and no disambiguation rule to break the tie. Reading these error messages is a core skill: Gradle prints the requested attributes and, for each candidate variant, which attributes matched, mismatched, or were unspecified. ## Custom attributes You can define your own `Attribute.of("com.acme.flavor", String::class.java)`, set it on producer variants and consumer configurations, and register compatibility/disambiguation rules so Gradle can match them — this is how Android flavors and similar custom dimensions work. ```kotlin val flavor = Attribute.of("com.acme.flavor", String::class.java) dependencies.attributesSchema { attribute(flavor) { compatibilityRules.add(FlavorCompatRule::class.java) disambiguationRules.add(FlavorDisambigRule::class.java) } } abstract class FlavorCompatRule : AttributeCompatibilityRule<String> { override fun execute(details: CompatibilityCheckDetails<String>) { if (details.consumerValue == "free" && details.producerValue == "paid") { details.compatible() // paid satisfies a free request } } } ```
- What error do you get when two variants survive compatibility but nothing breaks the tie?An 'ambiguous variant selection' error that lists the tied candidate variants and which attributes matched, so you can add a disambiguation rule or request more specific attributes.
- Why is a JVM-8 variant compatible with a consumer running on JVM 17 but not the reverse?Newer JVMs execute older bytecode, so JVM-8 bytecode runs on 17; JVM-17 bytecode cannot run on 8, so the built-in TargetJvmVersion compatibility rule rejects it.
saying these in an interview costs you the question
- Conflating compatibility and disambiguation — saying one rule does both.
- Assuming all attributes match only on exact equality (ignores registered rules like TargetJvmVersion).