skip to content

An extra artifact attached via publication.artifact(...) appears in the POM but a Gradle consumer resolving by attributes can't 'see' it. Why, and what are the implications?

level: seniorimportance: nice to knowfreq 22%

answer

  1. POM = flat file list by classifier
  2. GMM = variants with attributes
  3. raw artifact -> no variant -> no attribute match
  4. consumer must name classifier explicitly
  5. selectable file -> model as variant

basics

~20 s

Raw artifact(...) files are listed in the POM by classifier, so Maven consumers can request them, but they aren't described as a variant in Gradle Module Metadata. Gradle's attribute-based resolution only selects variants, so it ignores these loose files.

solid answer

~50 s

`publication.artifact(...)` attaches a file to the coordinates and writes it into the **POM** (and the bare file list), but it does **not** create a *variant* with attributes in **Gradle Module Metadata** (`.module`). Gradle's dependency resolution is variant-aware: a consumer requests attributes (e.g. `usage=java-runtime`, an OS attribute, a capability) and Gradle matches a *variant* from the metadata. A loose classified artifact has no attributes, so attribute-based selection can't target it — a Gradle consumer must explicitly ask for the classifier via an artifact selector (`dependency(...) { artifact { classifier = "all" } }`). The implication: for files you want *automatically* selected by Gradle consumers (per-platform jars, optional features), you should model them as **feature variants / outgoing variants** (consumable configurations with attributes) rather than raw artifacts. Raw artifacts are fine for Maven-style optional downloads (`-all`, `-docs`) that consumers fetch by classifier on demand.

code

kotlin · 11 lines
kotlin
// Raw artifact: POM-only, consumer must request the classifier explicitly
publishing.publications.named<MavenPublication>("maven") {
    artifact(tasks.named("fatJar")) { classifier = "all" }
}

// Consumer side — attribute resolution will NOT find -all; you must ask:
dependencies {
    implementation("com.example:mylib:1.0") {
        artifact { classifier = "all"; extension = "jar" }
    }
}

go deeper

for a junior

Recognize that an extra artifact shows up in the POM and is requested by classifier.

for a middle

Explain that POM lists files but Gradle Module Metadata describes variants, and raw artifacts aren't variants.

for a senior

Articulate why attribute-based resolution ignores raw artifacts and when to switch to feature/outgoing variants.

for a principal

Decide org-wide whether per-platform/optional content is modeled as classifier artifacts (Maven interop) or variants (Gradle-native selection), trading interoperability against rich resolution.

## Two publishing models living side by side When you publish, Gradle can write two metadata files: - **`pom.xml`** — the Maven view: coordinates, dependencies, and a flat list of files distinguished by classifier/type. Maven and older Gradle consumers read this. - **`*.module` (Gradle Module Metadata, GMM)** — Gradle's richer view: **variants**, each with **attributes** (usage, category, OS, etc.), **capabilities**, and the files belonging to that variant. ## What `artifact(...)` touches A raw `artifact(tasks.named("fatJar")) { classifier = "all" }`: - Adds the file to the published files and references it in the POM by classifier. - Does **not** add a new variant to GMM. There is no attribute set describing *when* a consumer should pick this file. ## Why Gradle consumers can't auto-select it Gradle resolution is **variant matching**: the consumer declares requested attributes on a resolvable configuration, and Gradle finds the producer **variant** whose attributes are compatible. A classified file with no attributes simply isn't a candidate in that matching — it's invisible to attribute-based selection. The consumer can still fetch it, but only by *explicitly* naming the classifier: ```kotlin dependencies { implementation("com.example:mylib:1.0") { artifact { classifier = "all"; extension = "jar" } } } ``` This bypasses variant resolution and pulls just that file — losing transitive metadata for that artifact. ## The design implication If the extra file represents a **selectable capability** — a Linux vs Windows native jar, a `-jdk8` vs `-jdk11` build, an optional feature with its own dependencies — model it as a **variant**: register a consumable configuration (`configurations.consumable(...)`), set attributes, attach the artifact to *that* configuration, and let it flow into GMM (e.g. via `java.registerFeature(...)` for feature variants, or a custom `AdhocComponentWithVariants`). Then Gradle consumers select it automatically by attributes/capabilities. If the file is just an **optional download** with no resolution semantics (a fat `-all` jar, a `-docs` zip for humans), a raw `artifact(...)` is the right, simpler tool — you accept that it's POM-only and fetched by classifier. ## Summary table | Need | Use | |------|-----| | Optional file fetched by classifier, Maven-friendly | raw `artifact(...)` | | File auto-selected by Gradle attributes/capabilities | variant (feature variant / outgoing variant) | This boundary is the crux: `artifact(...)` is about *attaching files*, not about *describing resolution*.

  • How would you make the extra file auto-selectable by Gradle consumers?
    Model it as a variant: create a consumable configuration with attributes (or use `java.registerFeature` / an outgoing variant), attach the artifact there so it lands in Gradle Module Metadata. Then consumers match it by attributes/capabilities.
  • What does a Gradle consumer lose when fetching a raw classified artifact?
    It bypasses variant resolution and gets just that file with no variant-level transitive dependencies or attribute matching — purely a Maven-style classifier fetch.

A raw artifact is like extra items stuffed in a parcel with a sticky note — you can ask for them by name, but the shipping label (the metadata Gradle reads to auto-route) doesn't mention them. A variant is printed on the label, so the sorting machine routes it automatically.

saying these in an interview costs you the question

  • Asserting that raw artifacts appear as variants in Gradle Module Metadata — they don't.
  • Telling a Gradle consumer they'll automatically resolve a classified artifact by usage attribute.
  • Conflating 'attached file' (artifact) with 'selectable variant' (configuration + attributes).

context