skip to content

Components and Variants

What a publication really contains: software components, Gradle Module Metadata, sources and javadoc jars, extra artifacts, archive configuration, and feature variants. Interviewers ask because this is where Gradle's model outgrows a plain POM.

on this pageshow

explore

questions

30

How do the archiveBaseName, archiveVersion, and archiveClassifier properties on an archive task (like Jar) combine to form the final published artifact file name?

level: juniorimportance: must knowfreq 60%

answer

  1. baseName-appendix-version-classifier.extension
  2. AbstractArchiveTask base class
  3. lazy Property<String>
  4. empty part drops its dash
  5. classifier distinguishes same-GAV artifacts

basics

~10 s

Gradle assembles the name as [baseName]-[appendix]-[version]-[classifier].[extension]. So baseName 'app', version '1.0', classifier 'sources' produces app-1.0-sources.jar. Empty parts are skipped along with their dash.

solid answer

~40 s

Archive tasks (`Jar`, `Zip`, `Tar`) compute `archiveFileName` from component `Property` values in a fixed order: `archiveBaseName`-`archiveAppendix`-`archiveVersion`-`archiveClassifier`.`archiveExtension`. Each is a lazy `Property<String>`, so you set them with `.set(...)` (Kotlin) or via assignment in Groovy. Empty/absent components are omitted together with their separating hyphen, so a missing classifier yields `app-1.0.jar` rather than a dangling dash. Defaults come from the project: `archiveBaseName` defaults to the project name, `archiveVersion` to `project.version`, and `archiveExtension` to the task type (jar/zip/tar). For publishing you typically leave baseName/version as defaults and set `archiveClassifier` to distinguish artifacts (e.g. `sources`, `javadoc`) that share the same coordinates. Because these are providers, they're resolved lazily at execution time, so referencing `project.version` set later still works.

code

kotlin · 10 lines
kotlin
tasks.named<Jar>("jar") {
    archiveBaseName.set("my-lib")
    archiveVersion.set("1.2.0")
    archiveClassifier.set("")        // -> my-lib-1.2.0.jar
}

tasks.register<Jar>("sourcesJar") {
    archiveClassifier.set("sources") // -> my-lib-1.2.0-sources.jar
    from(sourceSets.main.get().allSource)
}

go deeper

for a junior

Recall the component order and that empty parts drop their dash; give the sources-jar classifier example.

for a middle

Explain the lazy Property nature, the defaults (project name / project.version / task type), and the read-only archiveFileName/archiveFile providers.

for a senior

Tie classifier usage to keeping multiple artifacts under one GAV distinct and to how the maven-publish layer maps them; note lazy resolution interplay with versions set by plugins.

for a principal

Discuss standardizing archive naming conventions across a multi-module org build via a convention plugin so every module produces consistently named, publishable artifacts.

## What archive tasks are Gradle's bundling tasks — `Jar`, `Zip`, `Tar` (and subtypes like `War`, `Ear`) — all extend `AbstractArchiveTask`. That base class defines the naming contract used for every produced artifact, including the ones you publish to a Maven/Ivy repository. ## The naming components The final file name is built from these lazy properties, in this exact order: ``` [archiveBaseName]-[archiveAppendix]-[archiveVersion]-[archiveClassifier].[archiveExtension] ``` - `archiveBaseName: Property<String>` — defaults to the **project name**. - `archiveAppendix: Property<String>` — usually empty; used for things like a feature-variant name. - `archiveVersion: Property<String>` — defaults to `project.version`. - `archiveClassifier: Property<String>` — empty for the main artifact; `sources`, `javadoc`, `tests`, etc. for secondary artifacts. - `archiveExtension: Property<String>` — defaults to the archive type (`jar`, `zip`, `tar`). There is also a read-only `archiveFileName: Provider<String>` (the assembled name) and `archiveFile: Provider<RegularFile>` (the location under `build/libs` or `build/distributions`). ## Empty-part handling If a component is absent or empty, **both it and its leading hyphen are dropped**. So with no classifier you get `app-1.0.jar`, not `app-1.0-.jar`. This is why you never need to conditionally build the name yourself. ## Why these are Providers Each property is a lazy `Property<String>`. You configure it with `.set(...)` (or `=` in Groovy/Kotlin assignment). Lazy evaluation means a value derived from `project.version` is read **at execution time**, so it still reflects a version assigned later in the build script or by a plugin. ## Publishing relevance When you publish multiple jars under the same GAV coordinates (main, sources, javadoc), the **classifier** is what keeps their file names distinct and tells the Maven layer which artifact is which. Setting `archiveClassifier` correctly is therefore central to a clean publication. ```kotlin tasks.named<Jar>("jar") { archiveBaseName.set("my-lib") archiveVersion.set(project.version.toString()) archiveClassifier.set("") // main artifact } ```

  • What does archiveBaseName default to if you never set it?
    The project's name (`project.name`).
  • Why use .set() instead of plain assignment in Kotlin DSL?
    These are lazy `Property` objects; `.set()` (or `=` via the Kotlin property convention) records a value/provider resolved at execution time rather than eagerly.

saying these in an interview costs you the question

  • Saying you must manually concatenate the file name with conditionals for missing parts.
  • Claiming archiveVersion defaults to '1.0' rather than project.version.
  • Confusing the deprecated baseName/classifier (no 'archive' prefix) with the current archive* properties.

context

open as a page

Where in the build script do you attach a custom artifact, and what's the minimal correct snippet to add a fat JAR to a Maven publication?

level: juniorimportance: must knowfreq 48%

basics

~10 s

Inside publishing { publications { ... } }, on the publication, call artifact(tasks.named("fatJar")). That single line adds the fat JAR as an extra file on that publication's coordinates.

open as a page

How do you make a Gradle Java library publish a sources jar and a javadoc jar alongside the main artifact?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Inside the java { } block call withSourcesJar() and withJavadocJar(). Gradle registers sourcesJar and javadocJar tasks and attaches them to the java component, so they publish automatically.

open as a page

What is the Gradle Module Metadata (the `.module` file), and what does it describe that a traditional Maven POM cannot?

level: juniorimportance: must knowfreq 55%

basics

~20 s

It's a JSON file (module.module) Gradle publishes alongside the POM. It describes a module's variants, their dependencies, and attributes — richer info than a flat POM can express, so Gradle can pick the right variant when resolving.

open as a page

What is a SoftwareComponent in Gradle, and how does it relate to what gets published?

level: juniorimportance: must knowfreq 55%

basics

~10 s

A SoftwareComponent describes what a project produces for publication (its artifacts and dependencies). The MavenPublication's from(components.java) reads a component to populate the artifacts and POM/metadata that get published.

open as a page

When publishing a library, why must each additional jar (sources, javadoc, etc.) set a distinct archiveClassifier, and what goes wrong if two jars share the same classifier?

level: middleimportance: must knowfreq 50%

basics

~20 s

All jars in one publication share the same group:name:version, so the classifier is the only thing that distinguishes them. Two jars with the same classifier collide — the publication fails or one overwrites the other.

open as a page

How do you publish an optional feature variant with the java-library plugin using registerFeature, and what does usingSourceSet do?

level: middleimportance: must knowfreq 40%

basics

~10 s

In the java block call registerFeature("name") { usingSourceSet(...) }. It creates nameApi/nameImplementation configurations and an optional variant with capability group:base-feature:version that consumers opt into via requireCapabilities.

open as a page

What is a capability in Gradle, and how does it differ from a module's GAV coordinates?

level: middleimportance: must knowfreq 45%

basics

~10 s

A capability is a logical 'what this provides' identifier (group:name:version) attached to a variant. Two modules offering the same capability conflict, so only one can be on the classpath.

open as a page

How do you attach an extra file (for example, a fat/uber JAR produced by a custom task) to a Maven publication in Gradle?

level: middleimportance: must knowfreq 62%

basics

~10 s

Inside the publication block call artifact(...), passing the task that produces the file, e.g. artifact(tasks.named("fatJar")). Gradle wires the task as a build dependency and uploads its output as an extra artifact.

open as a page

What does `withSourcesJar()` do beyond just creating a Jar task — how does it integrate with the java component and publishing?

level: middleimportance: must knowfreq 55%

basics

~10 s

It registers the sourcesJar task and attaches the jar as a secondary variant on the java SoftwareComponent. So from(components["java"]) publishes it automatically and Gradle Module Metadata describes it as a documentation variant.

open as a page

Walk through the structure of a `.module` file: what does a 'variant' entry contain, and how does Gradle use it during resolution?

level: middleimportance: must knowfreq 45%

basics

~20 s

Each variant in the .module JSON has a name, a map of attributes, a list of dependencies (and constraints), capabilities, and the files it provides. Gradle matches the consumer's requested attributes against these to pick one variant.

open as a page

What is AdhocComponentWithVariants and why would you use it when publishing?

level: seniorimportance: must knowfreq 45%

basics

~10 s

AdhocComponentWithVariants is a software component you can assemble yourself by mapping outgoing (consumable) configurations to published variants with addVariantsFromConfiguration, controlling exactly which configurations end up in the publication.

open as a page

How do you configure the MANIFEST.MF of a published Jar using the manifest {} block, and how can manifest attributes be shared across multiple jars?

level: middleimportance: should knowfreq 45%

basics

~10 s

Inside a Jar task, use the manifest {} block and call attributes(mapOf(...)) to add entries like 'Implementation-Version' or 'Main-Class' to META-INF/MANIFEST.MF. To reuse, build a shared manifest and merge it.

open as a page

What capability does the java-test-fixtures plugin create, and how does a consumer depend on another project's test fixtures?

level: middleimportance: should knowfreq 30%

basics

~10 s

The java-test-fixtures plugin adds a testFixtures source set and publishes a variant with capability group:name-test-fixtures. Consumers depend on it via testImplementation(testFixtures(project(":mod"))).

open as a page

What is the difference between the classifier and the extension on a ConfigurablePublishArtifact, and what happens if two artifacts collide on both?

level: middleimportance: should knowfreq 40%

basics

~20 s

The classifier is the suffix in the filename (e.g. -all), the extension is the file type (e.g. jar, zip). Together with coordinates they uniquely identify a file; two artifacts with the same classifier+extension clash and publishing fails.

open as a page

When you call `withJavadocJar()`, what does the resulting jar contain and which task does it depend on?

level: middleimportance: should knowfreq 40%

basics

~10 s

It packages the output of the built-in javadoc task (generated HTML docs) into a jar with classifier javadoc. The javadocJar task depends on javadoc, so running it triggers doc generation first.

open as a page

How are dependency constraints and rich versions (strictly/prefers/rejects) represented and used through Gradle Module Metadata?

level: middleimportance: should knowfreq 35%

basics

~10 s

GMM stores per-variant dependencyConstraints and rich version info (requires, prefers, strictly, rejects) as structured JSON. Consumers that read GMM honor them during resolution; the POM can only approximate this.

open as a page

What is the difference between components.java and components.javaPlatform, and when do you publish each?

level: middleimportance: should knowfreq 40%

basics

~10 s

components.java (from the java plugin) publishes a library: a jar plus its dependencies. components.javaPlatform (from the java-platform plugin) publishes a platform/BOM: only dependency constraints, no jar.

open as a page

How do a component's variants and their dependencies map into the generated Maven POM, and what is lost?

level: middleimportance: should knowfreq 38%

basics

~20 s

Each 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.

open as a page

Why are archive task naming properties modeled as lazy Property/Provider types, and what practical bug does this avoid when project.version is set by a plugin or later in the build?

level: seniorimportance: should knowfreq 30%

basics

~20 s

archiveBaseName/Version/Classifier are lazy Property<String> objects resolved at execution time. So if project.version is set later (e.g. by a versioning plugin), the jar still picks up the correct value instead of a stale one captured early.

open as a page

How do you make Gradle archive tasks produce byte-for-byte reproducible artifacts, and which settings are involved?

level: seniorimportance: should knowfreq 40%

basics

~10 s

On each archive task set isPreserveFileTimestamps = false and isReproducibleFileOrder = true. These zero out entry timestamps and sort entries deterministically so the same inputs always produce identical archive bytes.

open as a page

Two dependencies in your graph provide the same capability. What happens, and how do you resolve the conflict?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Gradle fails resolution with a capability conflict. You resolve it with resolutionStrategy.capabilitiesResolution.withCapability("g:n") { select(...) }, or by excluding/replacing one module so only one variant provides the capability.

open as a page

How do Gradle feature variants compare to Maven's <optional>true dependencies and to multiple Maven modules?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Maven 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.

open as a page

When you attach a raw File (not a task) as a publication artifact, why might the publish task fail, and how do you fix the build wiring lazily?

level: seniorimportance: should knowfreq 33%

basics

~20 s

A bare File carries no task dependency, so the publish task can run before the file is generated and fails (missing file). Fix it by adding builtBy(theTask) or by passing a Provider<RegularFile> from the task's output, which carries the dependency.

open as a page

How can you customize the generated `sourcesJar` task — for example, excluding files or adding extra inputs — given that you didn't create it yourself?

level: seniorimportance: should knowfreq 35%

basics

~10 s

withSourcesJar() registers a real Jar task named sourcesJar, so you configure it like any task: tasks.named<Jar>("sourcesJar") { exclude(...); from(...) }. You don't recreate it; you reach for it by name.

open as a page

A teammate added `tasks.register<Jar>("sourcesJar")` and `publication.artifact(sourcesJar)` instead of `withSourcesJar()`, and now consumers can't resolve sources via attributes. Why, and how do you fix it properly?

level: seniorimportance: should knowfreq 25%

basics

~20 s

artifact(sourcesJar) only attaches a file to the Maven publication; it doesn't create a documentation variant on the java component, so Gradle Module Metadata has no sources variant to match. Use withSourcesJar() (or attach the variant via AdhocComponentWithVariants).

open as a page

When publishing, Gradle sometimes emits Module Metadata warnings. What causes them, and how do `suppressAllPublicationWarnings` / `suppressPomMetadataWarningsFor` work?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Warnings appear when a variant can be represented in GMM but not in the POM (lossy mapping). You can silence them per-variant with suppressPomMetadataWarningsFor("name") or all of them with suppressAllPublicationWarnings() on the publication.

open as a page

How would you build a custom software component in a plugin to publish a non-standard artifact set, and what are the design trade-offs?

level: seniorimportance: should knowfreq 22%

basics

~10 s

Inject SoftwareComponentFactory, call factory.adhoc("name"), add it to project.components, create consumable configurations with attributes and artifacts, then addVariantsFromConfiguration to map them. Publish via from(components["name"]).

open as a page

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%

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.

open as a page

How does a consumer enable or disable Gradle Module Metadata resolution, and what are the trade-offs of turning it off on either side?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Consuming GMM is on by default. You can opt out per-repository (metadataSources only the POM) or disable producing it via the GenerateModuleMetadata task. Turning it off loses variant-aware resolution and rich-version fidelity.

open as a page