skip to content

Archive Tasks (Jar/Zip)

Configuring Jar, Zip, and Tar tasks for publication: archive names and classifiers, manifest contents, and the flags that make archives reproducible. Asked because reproducible archives are now an expected default rather than a nicety.

on this pageshow

questions

5

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

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

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