skip to content

Beyond the build timestamp, what other sources of non-determinism can break a reproducible Maven build, and how do you address them?

level: principalimportance: should knowfreq 22%

answer

  1. Build-Jdk in manifest -> pin toolchain
  2. SNAPSHOTs -> requireReleaseDeps
  3. locale/timezone -> set UTC/en
  4. generators stamp time -> disable
  5. maven.build.timestamp leaks current time

basics

~20 s

Besides timestamps, watch out for entry ordering, the JDK version baked into the manifest, locale/timezone effects, absolute paths, SNAPSHOT dependencies, and code generators that embed times. Pin the toolchain, avoid SNAPSHOTs, and normalize generated content.

solid answer

~40 s

`project.build.outputTimestamp` handles timestamps and ordering in the archive, but a build can still vary for other reasons. The manifest records `Build-Jdk` / `Build-Jdk-Spec`, so a different JDK changes bytes — pin the toolchain (maven-toolchains-plugin) and the manifest content. **SNAPSHOT dependencies** resolve to whatever was latest at build time, so two builds can link different code — release builds must use only fixed versions (enforce with the maven-enforcer-plugin's requireReleaseDeps). Locale and timezone can affect generated text or sorting — set them explicitly. Absolute paths or usernames leaking into resources break it — avoid embedding `${user.dir}`/home paths. Code generators (protobuf, JAXB, some annotation processors) may stamp timestamps — configure them to omit timestamps. Filtering with `${maven.build.timestamp}` reintroduces the current time. Finally, plugin versions matter: only reproducible-friendly versions qualify. Verify the whole picture with `artifact:check-buildplan` and `artifact:compare`.

code

xml · 13 lines
xml
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <executions>
    <execution>
      <id>no-snapshots</id>
      <goals><goal>enforce</goal></goals>
      <configuration>
        <rules><requireReleaseDeps/></rules>
      </configuration>
    </execution>
  </executions>
</plugin>

go deeper

for a junior

Knows timestamps are not the only thing that can vary.

for a middle

Can name a few extra sources (JDK, SNAPSHOTs, locale) and the property to avoid (maven.build.timestamp).

for a senior

Can configure enforcer/toolchains/locale and reason about generator timestamps.

for a principal

Treats reproducibility as a system property, pins every nondeterministic input across the org, and enforces it with gates plus byte-comparison verification.

## Timestamps are only the start Setting `project.build.outputTimestamp` fixes the *archive* layer. A truly reproducible build needs the *inputs* and *other generators* to be deterministic too. The common offenders: ## 1. Toolchain / JDK Maven Archiver writes manifest entries like `Build-Jdk-Spec`. Building on JDK 17 vs 21 changes those bytes, and the compiler itself can emit slightly different bytecode across versions. **Fix:** pin the JDK with the `maven-toolchains-plugin` and document the exact compiler version; some teams also strip volatile manifest entries. ## 2. SNAPSHOT dependencies A `-SNAPSHOT` dependency resolves to the latest snapshot at build time, so two builds may embed different code. **Fix:** release builds must depend only on fixed releases. Enforce with: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <executions> <execution> <id>no-snapshots</id> <goals><goal>enforce</goal></goals> <configuration> <rules><requireReleaseDeps/></rules> </configuration> </execution> </executions> </plugin> ``` ## 3. Locale and timezone Default locale/timezone can change generated text, number/date formatting, or sort order in generated files. **Fix:** set `-Duser.language=en -Duser.country=US -Duser.timezone=UTC` (or via surefire/plugin config) so output is stable. ## 4. Absolute paths and environment leakage Resource filtering or generators may embed `${user.dir}`, home directories, or hostnames. **Fix:** never bake machine-specific paths into artifacts. ## 5. Resource filtering with volatile properties `${maven.build.timestamp}` is the *current* time — filtering it into a resource reintroduces variance. **Fix:** use `project.build.outputTimestamp` instead where you need a build time, or drop it. ## 6. Code generators / annotation processors Tools like protobuf, JAXB/XJC, or some processors stamp generated sources with the current time. **Fix:** turn off their timestamp option (e.g. protoc `--*_out` flags, XJC `-no-header`). ## 7. Non-reproducible plugin versions Only versions of packaging plugins that fixed ordering/timestamp bugs qualify. **Fix:** keep packaging plugins current; `check-buildplan` flags bad versions. ## Verifying the whole picture ```bash mvn artifact:check-buildplan # configuration lint mvn clean verify artifact:compare # rebuild + byte diff ``` Reproducibility is a *system property*: every nondeterministic input must be pinned or normalized, then proven by comparison.

  • Why are SNAPSHOT dependencies a reproducibility problem?
    A SNAPSHOT resolves to the latest available build at resolution time, so two builds can embed different bytes of that dependency. Release builds should use only fixed versions, enforced via requireReleaseDeps.
  • How can the JDK affect reproducibility even with outputTimestamp set?
    The manifest records the build JDK and the compiler can emit slightly different bytecode across versions; pin the toolchain so everyone uses the same JDK.
  • Why is filtering ${maven.build.timestamp} into a resource dangerous?
    It injects the current wall-clock time into the artifact, which differs on every build and breaks byte-identity; use project.build.outputTimestamp instead.

saying these in an interview costs you the question

  • Believing project.build.outputTimestamp alone is sufficient regardless of dependencies, JDK, or generators.
  • Releasing with -SNAPSHOT dependencies and calling the build reproducible.
  • Ignoring locale/timezone-dependent generated content.

context