skip to content

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

level: seniorimportance: should knowfreq 40%

answer

  1. isPreserveFileTimestamps = false
  2. isReproducibleFileOrder = true
  3. withType(AbstractArchiveTask).configureEach
  4. constant timestamp + sorted entries
  5. also pin deps / stable manifest

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.

solid answer

~50 s

Archive tasks normally embed each entry's last-modified timestamp and order entries by filesystem traversal, so two builds of identical sources produce different bytes. Setting `isPreserveFileTimestamps = false` makes Gradle write a constant timestamp for every entry, and `isReproducibleFileOrder = true` sorts entries by a stable, platform-independent order. With both enabled on a `Jar`/`Zip`/`Tar`, identical inputs yield byte-identical archives. Apply them on every archive task — typically in a global `tasks.withType<AbstractArchiveTask>().configureEach { ... }` block so jar, sources jar, javadoc jar, and distribution zips are all covered. You must also keep other inputs deterministic: avoid volatile manifest attributes (timestamps, hostnames), pin dependency versions, and ensure generated resources are stable. Reproducible artifacts improve build-cache and remote-cache hit rates, let consumers verify published artifacts, and underpin supply-chain integrity. The two settings are the necessary archive-level switches; reproducibility overall is an input-determinism discipline.

code

kotlin · 4 lines
kotlin
tasks.withType<AbstractArchiveTask>().configureEach {
    isPreserveFileTimestamps = false
    isReproducibleFileOrder = true
}

go deeper

for a junior

Name the two flags and that they make archives identical for identical inputs.

for a middle

Explain what each flag does (constant timestamp vs sorted order) and apply them via withType().configureEach across all archives.

for a senior

Discuss the broader input-determinism requirements (manifest, pinned deps, codegen) and the build-cache / verification payoffs.

for a principal

Mandate reproducibility org-wide via a convention plugin and tie it to supply-chain verification and remote-cache efficiency policy.

## The problem A ZIP/JAR is a container of entries, each stamped with a last-modified time, written in whatever order the directory walk produced. Both vary between machines and runs, so two builds of the **same** source produce **different** bytes — defeating verification and cache reuse. ## The two archive switches `AbstractArchiveTask` exposes two booleans: - `isPreserveFileTimestamps` (default `true`). Set to `false` to write a **fixed constant timestamp** for every entry instead of the real mtime. - `isReproducibleFileOrder` (default `false`). Set to `true` to write entries in a **deterministic, sorted order** independent of the filesystem. ```kotlin tasks.withType<AbstractArchiveTask>().configureEach { isPreserveFileTimestamps = false isReproducibleFileOrder = true } ``` Applying it via `withType(...).configureEach` ensures **every** archive — main jar, sources jar, javadoc jar, distribution zip/tar — is covered without repeating yourself, and `configureEach` keeps it lazy (no eager task realization). ## Other inputs that must be deterministic The two flags fix archive packaging, but byte-equality also requires the **contents** to be stable: - **Manifest**: drop volatile attributes (build time, hostname, random build IDs). - **Dependencies**: pin versions / use a lockfile so the same classes are bundled. - **Generated code/resources**: ensure annotation processors, codegen, and property-file ordering are deterministic. - **Locale/encoding**: keep file encoding and sort locale stable. ## Why it matters - **Build cache**: identical outputs maximize local and remote build-cache hits; non-reproducible archives constantly miss. - **Verification & supply chain**: consumers (and tools like reproducible-builds verification, Sigstore, dependency verification) can confirm a published artifact matches its declared source. - **Diff-ability**: identical inputs yielding identical artifacts means a changed checksum genuinely signals a changed input. ## Relationship to publishing For a published library you want every artifact under your coordinates to be reproducible so downstream verifiers and your own cache behave predictably. These settings are therefore a standard part of a publishing-ready convention plugin.

  • Why apply the flags via withType(...).configureEach rather than on the jar task only?
    So every archive — sources jar, javadoc jar, distribution zips/tars — is reproducible too, and configureEach keeps configuration lazy without realizing tasks eagerly.
  • Do these two flags alone guarantee a reproducible build?
    No. They fix archive packaging, but you also need deterministic contents: stable manifest values, pinned dependencies, and reproducible code/resource generation.
  • How does reproducibility interact with Gradle's build cache?
    Deterministic outputs maximize cache hits locally and remotely; non-reproducible archives change their bytes each run and miss the cache.

saying these in an interview costs you the question

  • Setting isPreserveFileTimestamps = true thinking it enables reproducibility (it's the opposite).
  • Claiming the two flags alone fully guarantee reproducibility regardless of contents.
  • Applying them only to the jar task and forgetting sources/javadoc/distribution archives.

context