How do a component's variants and their dependencies map into the generated Maven POM, and what is lost?
answer
- POM = flat scopes (compile/runtime)
- attributes + capabilities lost in POM
- GMM preserves variant graph
- apiElements→compile, runtimeElements→runtime
- mapToMavenScope steers projection
basics
~20 sEach variant's dependencies are mapped to a Maven scope (compile/runtime) when generating the POM. The POM can only express a flat scope view, so variant attributes and capabilities are lost — Gradle Module Metadata preserves them.
solid answer
~40 sWhen you publish a component, Gradle renders two metadata files: a `pom.xml` and a `*.module` (Gradle Module Metadata). The component's variants — e.g. `apiElements` and `runtimeElements` from `components.java` — carry dependency lists with rich information (attributes, capabilities, version constraints, exclusions). The **POM** is a lossy projection: `apiElements` deps become `compile` scope, `runtimeElements`-only deps become `runtime` scope, and that's roughly all Maven understands. Variant **attributes**, **capabilities**, and the fact that there are *multiple* distinct variants collapse into a single flat dependency list. **GMM** is the richer file that preserves the full variant graph, so a Gradle consumer using variant-aware resolution gets exact selection while a Maven consumer falls back to the flattened POM. You can influence the POM mapping per variant via `mapToMavenScope`/`mapToOptional`/`skip` on an adhoc component.
code
groovy · 6 lines// POM rendered from components.java (flattened):
// <dependency><groupId>g</groupId><artifactId>a</artifactId>
// <version>1.0</version><scope>compile</scope></dependency>
//
// GMM (.module) keeps apiElements & runtimeElements as separate
// variants with org.gradle.usage attributes and capabilities.go deeper
Know dependencies map to compile/runtime scopes in the POM.
Explain that the POM is lossy and GMM preserves variants/attributes/capabilities.
Discuss steering the projection with mapToMavenScope/optional/skip and debugging via the .module file.
Frame GMM vs POM as a producer-contract decision affecting cross-ecosystem (Maven vs Gradle) consumers.
## Two outputs from one component Publishing a software component produces: - **`pom.xml`** — the Maven-format descriptor every Maven/Gradle consumer can read. - **`<module>-<version>.module`** — **Gradle Module Metadata (GMM)**, Gradle's richer JSON format. ## What the POM captures The POM expresses dependencies in **scopes** (`compile`, `runtime`, `provided`, `test`, etc.). Gradle maps your component's variants to these: - Dependencies in the **`apiElements`** variant → `compile` scope (visible to consumers' compilation). - Dependencies only in **`runtimeElements`** → `runtime` scope. That's essentially the limit of POM expressiveness for this purpose. ## What the POM loses A Gradle variant carries far more than a Maven scope: - **Attributes** (e.g. `org.gradle.usage`, `org.gradle.jvm.version`, `Category`) used for precise variant selection — *not representable in a POM*. - **Capabilities** (used for feature variants / conflict detection) — *not in a POM*. - **Multiple distinct variants** for the same module (e.g. api vs runtime vs javadoc vs sources) — the POM flattens artifacts and dependencies. - **Rich version constraints** (strictly/prefer/require, rejections) — only partially expressible. ## Why GMM exists Because the POM can't carry the variant model, Gradle also writes **GMM**, which encodes every variant with its attributes, capabilities, dependencies, and files. When **both** consumer and producer use Gradle, resolution is *variant-aware* and uses GMM for exact selection; a Maven consumer (or `metadata.sources` excluding GMM) reads the flattened POM. ```json // excerpt of a .module file (GMM) "variants": [ { "name": "apiElements", "attributes": { "org.gradle.usage": "java-api" }, "dependencies": [ ... ] }, { "name": "runtimeElements", "attributes": { "org.gradle.usage": "java-runtime" }, "dependencies": [ ... ] } ] ``` ## Controlling the POM projection Via `AdhocComponentWithVariants.addVariantsFromConfiguration { ... }` you can steer the lossy mapping: - `mapToMavenScope("compile"|"runtime")` — force a scope. - `mapToOptional()` — mark deps `<optional>true</optional>`. - `skip()` — omit that configuration from the POM altogether. ## Practical implication Never assume the POM tells the whole story for a Gradle-published module. If a consumer reports a missing variant or wrong dependency, check the **`.module`** file, not just the POM — GMM is authoritative for Gradle-to-Gradle resolution.
- If a Maven user consumes a Gradle-published module, what do they see?The flattened POM with compile/runtime scopes; they don't get attribute/capability-based variant selection since Maven ignores GMM.
- Which file is authoritative for Gradle-to-Gradle resolution?The Gradle Module Metadata (.module) file, which preserves the full variant graph.
saying these in an interview costs you the question
- Claiming the POM preserves variant attributes/capabilities — it does not.
- Assuming Maven consumers get variant-aware selection.