Deepen PracticeOrdered learning track

Reproducible Builds and Determinism

Learn Maven In Action - Part 028

Reproducible builds, deterministic Maven artifacts, outputTimestamp, dependency and plugin pinning, generated source determinism, archive metadata, and verification workflow.

11 min read2024 words
PrevNext
Lesson 2840 lesson track2333 Deepen Practice
#maven#java#reproducible-builds#deterministic-builds+4 more

Part 028 — Reproducible Builds and Determinism

Target pembelajaran: setelah membaca part ini, kamu mampu mendesain Maven build yang output artifact-nya dapat dibuat ulang secara bit-by-bit atau setidaknya explainable secara forensik: source sama, dependency sama, environment sama, command sama, hasil sama.

Build yang “berhasil” belum tentu build yang bisa dipercaya.

Di organisasi kecil, build sukses berarti:

mvn clean package works on my machine

Di organisasi engineering matang, build sukses berarti:

Given the same source, dependency set, build instructions, and controlled environment,
we can recreate the same artifact and explain every byte that changed.

Reproducibility bukan kosmetik. Ia adalah fondasi untuk:

  • supply-chain security
  • incident forensics
  • artifact promotion
  • release rollback
  • compliance audit
  • build cache correctness
  • binary provenance
  • long-term maintainability

Maven memberi banyak mekanisme untuk mendukung ini, tetapi tidak otomatis menyelesaikan semua masalah. Kamu harus mengontrol dependency graph, plugin version, generated code, archive timestamp, environment input, dan repository policy.


1. Definisi: Repeatable, Reproducible, Deterministic

Istilah sering tercampur. Kita pisahkan.

IstilahArti PraktisContoh
RepeatableBuild bisa diulang oleh orang yang sama di environment sama.Laptop engineer menjalankan command dua kali dan hasilnya sama.
ReproduciblePihak lain bisa menghasilkan artifact identik dari source, environment, dan instruksi yang sama.CI clean runner dan release runner menghasilkan SHA-256 artifact sama.
DeterministicBuild logic tidak bergantung pada input tersembunyi seperti waktu, urutan file acak, path lokal, atau network drift.ZIP/JAR entry order dan timestamp stabil.
HermeticBuild tidak bergantung pada state luar yang tidak dideklarasikan.Semua dependency berasal dari repository yang dikontrol dan versi dipin.

Reproducible build bukan berarti “tidak pernah ada environment”. Artinya environment menjadi bagian dari input yang dikontrol.


2. Mental Model: Artifact adalah Function Output

Pikirkan build seperti fungsi:

artifact = build(source, pom, settings, dependencies, plugins, jdk, os, env, time, command)

Build reproducible berusaha mengubahnya menjadi:

artifact = build(locked_source, locked_build_model, locked_dependencies, locked_plugins, controlled_jdk, controlled_env)

Dan menghapus input tersembunyi:

artifact != build(current_time, random_order, local_path, latest_snapshot, network_state, developer_machine)

Diagram:

Senior Maven engineering adalah seni mengubah input implisit menjadi input eksplisit.


3. Official Maven Reproducible Build Baseline

Dokumentasi resmi Maven menjelaskan bahwa reproducible build menciptakan path yang independently-verifiable dari source ke binary, dan build disebut reproducible jika source code, environment, dan build instructions yang sama dapat menghasilkan copy artifact bit-by-bit identik.

Sumber resmi:

Maven ecosystem modern mendukung property penting:

<project.build.outputTimestamp>...</project.build.outputTimestamp>

Banyak archive plugin Maven membaca property ini sebagai timestamp entry archive agar output tidak bergantung pada waktu build saat ini.


4. Source of Non-Determinism

Penyebab artifact berubah walaupun source terlihat sama:

SourceContohDampak
TimestampJAR entry timestamp mengikuti waktu buildSHA artifact berubah setiap build.
File orderZIP entries mengikuti filesystem orderArtifact berbeda antar OS/filesystem.
Generated codeGenerator menulis timestamp/header/path lokalSource output berubah.
SNAPSHOT dependencySnapshot terbaru berubah di repositoryBytecode/classpath berubah.
Dynamic versionVersion range atau latest releaseDependency graph drift.
Plugin version unpinnedPlugin default berubahBuild behavior berubah.
Environment variableBuild membaca USER, HOME, locale, timezoneManifest/resource berubah.
Absolute pathGenerated file menyimpan /home/alice/...Artifact berbeda per machine.
Random valueUUID/build id dibuat saat packageArtifact unik setiap build.
Local repository stateDependency resolved dari cache rusak/berbedaBuild tidak sama antar runner.
Parallel raceGenerated file ditulis raceOutput kadang berbeda.

Kalau artifact SHA berubah, jangan langsung curiga malicious tampering. Pertama cari nondeterminism.


5. Control 1 — Pin Plugin Versions

Jangan bergantung pada implicit plugin version.

Buruk:

<plugin>
  <artifactId>maven-jar-plugin</artifactId>
</plugin>

Baik:

<properties>
  <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
  <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
  <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
</properties>

<build>
  <pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>${maven-jar-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
      </plugin>
    </plugins>
  </pluginManagement>
</build>

Kenapa plugin version penting?

Karena plugin adalah build logic. Jika plugin berubah, artifact bisa berubah walaupun source tidak berubah.

Rule:

Dependency version mengontrol runtime/compile graph.
Plugin version mengontrol build behavior.
Keduanya harus dipin.

6. Control 2 — Pin Dependency Graph

Gunakan dependencyManagement atau BOM yang jelas.

Jangan:

<version>[1.0,2.0)</version>

Jangan juga mengandalkan transitive version tanpa ownership.

Lebih baik:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.acme.platform</groupId>
      <artifactId>acme-platform-bom</artifactId>
      <version>${acme-platform.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Lalu dependency biasa tanpa version karena version dikontrol BOM:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>

Untuk audit:

mvn dependency:tree -DoutputFile=target/dependency-tree.txt

Untuk convergence:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <executions>
    <execution>
      <id>enforce-dependency-policy</id>
      <goals>
        <goal>enforce</goal>
      </goals>
      <configuration>
        <rules>
          <dependencyConvergence />
          <requireUpperBoundDeps />
        </rules>
      </configuration>
    </execution>
  </executions>
</plugin>

Reproducible classpath adalah prerequisite reproducible artifact.


7. Control 3 — Set project.build.outputTimestamp

Archive formats seperti JAR/ZIP menyimpan timestamp entry. Jika timestamp mengikuti waktu build, binary berubah setiap build.

Gunakan property:

<properties>
  <project.build.outputTimestamp>2026-01-01T00:00:00Z</project.build.outputTimestamp>
</properties>

Atau dalam release pipeline, set dari timestamp commit/tag yang stabil:

mvn clean verify \
  -Dproject.build.outputTimestamp="$(git log -1 --format=%cI)"

Namun hati-hati: command substitution di shell harus distandardisasi di CI. Lebih baik release pipeline menulis nilai eksplisit.

Pattern:

Development build:
  outputTimestamp boleh fixed default.

Release build:
  outputTimestamp berasal dari commit/tag timestamp yang immutable.

Rebuild verification:
  outputTimestamp harus sama dengan release metadata.

Maven JAR Plugin mendokumentasikan parameter outputTimestamp untuk reproducible archive entries, dengan default ${project.build.outputTimestamp}.


8. Control 4 — Avoid Build-Time Randomness

Jangan generate build id random ke artifact.

Buruk:

build.id=${random.uuid}
build.time=${current.timestamp}
build.user=${user.name}

Kalau harus punya metadata, pisahkan:

MetadataReproducible?Rekomendasi
Git commit SHAYa, jika source samaBoleh.
Git tagYa, jika tag immutableBoleh.
Build time saat CITidakHindari di binary. Simpan di external release metadata.
Build runner idTidakJangan masukkan artifact.
UsernameTidakJangan masukkan artifact.
Absolute workspace pathTidakJangan masukkan artifact.

Manifest yang lebih baik:

Implementation-Title: case-service
Implementation-Version: 1.4.2
Build-Commit: 7f3a9d4c

Manifest yang buruk:

Built-By: alice
Build-Time: 2026-07-03T03:17:44.912Z
Build-Path: /Users/alice/work/case-service

9. Control 5 — Make Generated Sources Deterministic

Generated source adalah salah satu penyebab terbesar artifact drift.

Generated output harus memenuhi invariant:

same input contract + same generator version + same config = same generated files

Checklist generator:

[ ] Generator version dipin.
[ ] Input schema/contract berada di repository atau artifact versioned.
[ ] Output berada di target/generated-*.
[ ] Tidak ada timestamp di header generated file.
[ ] Tidak ada absolute path di comment generated file.
[ ] Sorting field/method/class deterministic.
[ ] Locale/timezone tidak mempengaruhi output.
[ ] Generated output bisa dibuat ulang dari clean checkout.

Contoh buruk generated header:

// Generated at 2026-07-03T10:12:31+07:00 by alice on /Users/alice/work/project

Contoh lebih baik:

// Generated by acme-contract-generator 2.3.1 from case-api.yaml

Kalau generator selalu menulis timestamp dan tidak bisa dimatikan, reproducibility byte-for-byte tidak tercapai. Kamu harus:

  • cari opsi disable timestamp
  • patch/configure generator
  • post-process output dengan hati-hati
  • atau dokumentasikan sebagai known non-reproducible component

10. Control 6 — Keep Generated Output Out of Source Control, Usually

Default policy:

Generated output tidak dicommit.
Generator input dan config dicommit.

Exception:

  • consumer tidak punya generator
  • generated source adalah compatibility artifact yang di-review sebagai API
  • generator mahal/tidak tersedia di build environment
  • regulatory process mengharuskan generated code direview sebagai source

Kalau generated output dicommit, policy harus lebih ketat:

[ ] Ada command resmi untuk regenerate.
[ ] CI memverifikasi committed generated output sama dengan regenerated output.
[ ] Generator version dipin.
[ ] Generated diff tidak boleh manual-edit.

CI pattern:

mvn clean generate-sources
if ! git diff --exit-code; then
  echo "Generated sources are out of date"
  exit 1
fi

Tapi pattern ini hanya valid jika generator output deterministic.


11. Control 7 — Control Repository Resolution

Dependency reproducibility tidak hanya soal POM. Repository juga harus dikontrol.

Risiko:

  • dependency dihapus dari remote repository
  • metadata berubah
  • SNAPSHOT berubah
  • mirror berbeda antar developer dan CI
  • internal repository proxy menyimpan artifact berbeda dari repository lain
  • corrupted local cache

Enterprise pattern:

Rule:

Developer, CI, and release builds must resolve through the same repository policy.

Use settings.xml mirror:

<mirrors>
  <mirror>
    <id>company-maven</id>
    <mirrorOf>*</mirrorOf>
    <url>https://maven.company.example/repository/maven-all</url>
  </mirror>
</mirrors>

Jangan biarkan release build mengambil dependency langsung dari internet jika policy organisasi membutuhkan audit.


12. Control 8 — SNAPSHOT Policy

SNAPSHOT adalah mutable by design.

Gunakan SNAPSHOT untuk development integration, bukan release reproducibility.

Policy:

Build TypeSNAPSHOT Allowed?Reason
Local devYaIterasi cepat.
Feature branch CIKadangBergantung policy internal.
Main branch verificationSebaiknya tidak untuk external dependencyMengurangi drift.
Release candidateTidakHarus immutable.
Production releaseTidakArtifact harus rebuildable.

Enforcer dapat dipakai untuk melarang SNAPSHOT di release profile:

<requireReleaseDeps>
  <message>No SNAPSHOT dependencies are allowed in release builds.</message>
</requireReleaseDeps>

Snapshot internal masih bisa berguna, tetapi jangan campur dengan release artifact yang harus bisa diaudit bulan atau tahun berikutnya.


13. Control 9 — JDK and Toolchain Determinism

Bytecode bisa berubah karena JDK berbeda.

Control:

<properties>
  <maven.compiler.release>17</maven.compiler.release>
</properties>

Dan jika perlu:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-toolchains-plugin</artifactId>
  <executions>
    <execution>
      <goals>
        <goal>toolchain</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <toolchains>
      <jdk>
        <version>17</version>
        <vendor>temurin</vendor>
      </jdk>
    </toolchains>
  </configuration>
</plugin>

Practical policy:

Maven runtime JDK = allowed set.
Compiler target JDK = explicit.
Toolchain JDK = controlled for release builds.
CI image = versioned.

Jangan hanya berkata “pakai Java 17”. Tentukan distribusi, patch policy, dan container image untuk release build jika reproducibility penting.


14. Control 10 — Locale, Timezone, Encoding

Set property umum:

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

CI environment:

export TZ=UTC
export LANG=C.UTF-8
export LC_ALL=C.UTF-8

Kenapa?

  • sorting bisa dipengaruhi locale
  • generated text bisa memakai locale default
  • timestamp formatting bisa dipengaruhi timezone
  • resource filtering bisa membaca encoding berbeda

Kalau build menghasilkan file text dari template/generator, encoding dan locale harus dianggap input build.


15. Control 11 — Archive Entry Order and Metadata

Artifact Java biasanya archive: JAR/WAR/EAR/ZIP/TAR.

Non-determinism archive:

  • entry timestamp
  • entry order
  • file mode/permission
  • username/group metadata di tar
  • compression metadata
  • manifest ordering

Modern Maven archive plugins makin mendukung reproducibility, tapi kamu tetap perlu:

[ ] Pin plugin version.
[ ] Set project.build.outputTimestamp.
[ ] Avoid custom archiving scripts.
[ ] Inspect archive entries.
[ ] Avoid embedding local metadata.

Inspect:

jar tvf target/*.jar | head -50
jar tf target/*.jar | sort > target/jar-entries.txt
sha256sum target/*.jar

Untuk zip/tar assembly, cek plugin docs apakah mendukung output timestamp dan deterministic ordering.


16. Build Verification Workflow

Reproducibility harus dibuktikan, bukan diasumsikan.

Local simple test:

rm -rf ~/.m2/repository/com/acme/case-service
mvn clean verify
sha256sum target/*.jar > /tmp/build-1.sha256

mvn clean verify
sha256sum target/*.jar > /tmp/build-2.sha256

diff -u /tmp/build-1.sha256 /tmp/build-2.sha256

Better clean-room test dengan dua workspace:

git clone <repo> /tmp/build-a
git clone <repo> /tmp/build-b

cd /tmp/build-a
mvn -s /path/to/settings.xml clean verify
sha256sum target/*.jar > /tmp/a.sha256

cd /tmp/build-b
mvn -s /path/to/settings.xml clean verify
sha256sum target/*.jar > /tmp/b.sha256

diff -u /tmp/a.sha256 /tmp/b.sha256

Even better: dua runner/container berbeda dengan image sama.


17. If SHA Differs: Forensic Playbook

Step 1 — Compare artifact listing

mkdir /tmp/a /tmp/b
cp build-a/target/app.jar /tmp/a/app.jar
cp build-b/target/app.jar /tmp/b/app.jar

jar tf /tmp/a/app.jar | sort > /tmp/a/files.txt
jar tf /tmp/b/app.jar | sort > /tmp/b/files.txt

diff -u /tmp/a/files.txt /tmp/b/files.txt

Jika daftar file berbeda, masalahnya source/resource inclusion.

Step 2 — Compare timestamps

jar tvf /tmp/a/app.jar > /tmp/a/listing.txt
jar tvf /tmp/b/app.jar > /tmp/b/listing.txt

diff -u /tmp/a/listing.txt /tmp/b/listing.txt

Jika hanya timestamp berbeda, cek project.build.outputTimestamp dan plugin support.

Step 3 — Extract and compare content

mkdir /tmp/a/extract /tmp/b/extract
(cd /tmp/a/extract && jar xf ../app.jar)
(cd /tmp/b/extract && jar xf ../app.jar)

diff -ru /tmp/a/extract /tmp/b/extract

Jika .class berbeda, lanjut:

javap -classpath /tmp/a/extract -verbose com.acme.App > /tmp/a/App.javap
javap -classpath /tmp/b/extract -verbose com.acme.App > /tmp/b/App.javap

diff -u /tmp/a/App.javap /tmp/b/App.javap

Step 4 — Compare dependency tree

mvn dependency:tree -DoutputFile=target/dependency-tree.txt

Diff antara build A dan B.

Step 5 — Compare effective POM

mvn help:effective-pom -Doutput=target/effective-pom.xml

Diff effective POM.

Jika effective POM berbeda, masalahnya profile, property, parent, settings, atau environment.


18. Reproducibility Levels

Tidak semua organisasi langsung butuh bit-by-bit reproducibility penuh untuk semua artifact. Buat maturity level.

LevelCapabilityCocok Untuk
0Build works sometimesPrototype, bukan production.
1Clean build repeatable di CIMinimum production.
2Dependency/plugin versions pinnedTeam production.
3Artifact timestamp controlledRelease engineering dasar.
4Clean-room rebuild menghasilkan SHA samaHigh-trust internal platform.
5Independent third-party rebuild possibleOpen-source/security-critical/regulatory.

Untuk enterprise case management/regulatory platform, target minimal realistis:

Level 3 untuk semua service.
Level 4 untuk shared libraries, contract artifacts, release candidates, dan compliance-critical components.

19. Reproducible Release Pipeline Pattern

Pipeline:

Important release metadata:

source commit SHA
Git tag
Maven version
JDK version/vendor
CI image digest
settings.xml reference
repository mirror URL
project.build.outputTimestamp
resolved dependency tree
resolved plugin versions
effective POM
artifact checksums
SBOM

Artifact tanpa metadata build sulit diaudit.


20. What Not to Put Inside the Artifact

Untuk reproducibility dan security, jangan masukkan:

current timestamp
CI job URL as required runtime metadata
workspace path
developer username
machine hostname
random UUID
production secrets
repository credentials
mutable environment config
large local reports
logs

Kalau metadata itu berguna, simpan sebagai external build attestation/release record, bukan di binary runtime.

Exception: version/commit metadata yang immutable dan berguna untuk runtime diagnostics boleh masuk manifest atau /version.properties.


21. Maven Artifact Plugin and Buildinfo

Maven Artifact Plugin menyediakan goal/report yang membantu diagnosa reproducible build, termasuk membandingkan output build dengan reference. Dokumentasi Maven Artifact Plugin menunjukkan workflow seperti menjalankan build dan menggunakan artifact:compare untuk memeriksa perbedaan reproducible build.

Sumber resmi:

Dalam organisasi, kamu bisa menggunakan buildinfo/rebuild metadata untuk menjawab:

Artifact ini dibangun dari source apa?
Dengan Maven/JDK/plugin/dependency apa?
Apakah artifact ini bisa dibuat ulang?
Apa perbedaan build sekarang dengan release sebelumnya?

22. Example Parent POM Policy

<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.acme.platform</groupId>
  <artifactId>acme-build-parent</artifactId>
  <version>1.0.0</version>
  <packaging>pom</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

    <!-- Release pipeline may override this with immutable commit/tag timestamp. -->
    <project.build.outputTimestamp>2026-01-01T00:00:00Z</project.build.outputTimestamp>

    <maven.compiler.release>17</maven.compiler.release>

    <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
    <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
    <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
    <maven-failsafe-plugin.version>3.5.3</maven-failsafe-plugin.version>
    <maven-enforcer-plugin.version>3.5.0</maven-enforcer-plugin.version>
  </properties>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${maven-compiler-plugin.version}</version>
          <configuration>
            <release>${maven.compiler.release}</release>
          </configuration>
        </plugin>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-jar-plugin</artifactId>
          <version>${maven-jar-plugin.version}</version>
        </plugin>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven-surefire-plugin.version}</version>
        </plugin>

        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>${maven-failsafe-plugin.version}</version>
        </plugin>
      </plugins>
    </pluginManagement>

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-enforcer-plugin</artifactId>
        <version>${maven-enforcer-plugin.version}</version>
        <executions>
          <execution>
            <id>enforce-reproducible-build-policy</id>
            <goals>
              <goal>enforce</goal>
            </goals>
            <configuration>
              <rules>
                <requireMavenVersion>
                  <version>[3.9.0,)</version>
                </requireMavenVersion>
                <requireJavaVersion>
                  <version>[17,)</version>
                </requireJavaVersion>
                <requirePluginVersions />
                <dependencyConvergence />
              </rules>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Catatan:

  • Versi plugin di atas adalah contoh baseline yang harus kamu validasi sesuai repository perusahaan.
  • Jangan copy-paste angka versi tanpa dependency update policy.
  • Parent POM harus punya release cycle sendiri.

23. Example CI Verification Step

name: maven-reproducibility-check

on:
  pull_request:
  push:
    branches: [ main ]

jobs:
  reproducibility:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'
          cache: maven

      - name: Build first artifact
        run: |
          export TZ=UTC
          mvn -B -V clean verify \
            -Dproject.build.outputTimestamp=2026-01-01T00:00:00Z
          mkdir -p /tmp/build-1
          cp target/*.jar /tmp/build-1/
          sha256sum /tmp/build-1/*.jar > /tmp/build-1.sha256

      - name: Build second artifact
        run: |
          export TZ=UTC
          mvn -B -V clean verify \
            -Dproject.build.outputTimestamp=2026-01-01T00:00:00Z
          mkdir -p /tmp/build-2
          cp target/*.jar /tmp/build-2/
          sha256sum /tmp/build-2/*.jar > /tmp/build-2.sha256

      - name: Compare checksums
        run: |
          diff -u /tmp/build-1.sha256 /tmp/build-2.sha256

Untuk multi-module, artifact path harus disesuaikan:

find . -path '*/target/*.jar' -not -path '*/target/original-*' -print0 \
  | sort -z \
  | xargs -0 sha256sum

24. Common Failure Modes

Failure 1 — Checksum Berbeda Setiap Build

Kemungkinan:

  • timestamp archive belum dikontrol
  • generated file menulis waktu build
  • manifest memasukkan build time

Fix:

<project.build.outputTimestamp>2026-01-01T00:00:00Z</project.build.outputTimestamp>

Lalu inspect extracted diff.


Failure 2 — Developer dan CI Menghasilkan Dependency Berbeda

Kemungkinan:

  • settings mirror berbeda
  • local repository developer punya artifact lama
  • SNAPSHOT berubah
  • profile aktif berbeda

Fix:

mvn -s ci-settings.xml help:effective-settings
mvn help:effective-pom -Doutput=target/effective-pom.xml
mvn dependency:tree -DoutputFile=target/dependency-tree.txt

Failure 3 — Release Tidak Bisa Dibuat Ulang Setelah 6 Bulan

Kemungkinan:

  • dependency external hilang/berubah
  • plugin version tidak dipin
  • repository tidak menyimpan cached artifact
  • generated tool tidak tersedia
  • release metadata tidak disimpan

Fix jangka panjang:

Use repository manager.
Archive build metadata.
Pin plugin/dependency versions.
Ban dynamic versions.
Store generator artifacts.
Keep release branch/tag immutable.

Failure 4 — Reproducibility Rusak Setelah Menambah Code Generator

Kemungkinan:

  • generator menulis timestamp
  • file order mengikuti input directory listing
  • generator version floating
  • generated source dicommit tapi tidak divalidasi

Fix:

Pin generator.
Disable timestamp/header.
Sort input files.
Generate into target/.
Add CI diff check if generated output committed.

25. What Good Looks Like

Project Maven yang matang punya sifat berikut:

[ ] mvn clean verify works from clean checkout.
[ ] Plugin versions are pinned in parent pluginManagement.
[ ] Dependency versions are owned by BOM/dependencyManagement.
[ ] No dynamic versions in release build.
[ ] No SNAPSHOT dependencies in release artifacts.
[ ] project.build.outputTimestamp is set.
[ ] Generated sources are deterministic.
[ ] Generated outputs are under target/ unless explicitly governed.
[ ] Build environment is versioned or containerized.
[ ] Effective POM and dependency tree can be archived.
[ ] Artifact checksum is generated and stored.
[ ] Release artifact can be rebuilt or differences can be explained.

26. Exercises

Exercise 1 — Timestamp Drift Test

Ambil module JAR.

mvn clean package
sha256sum target/*.jar
sleep 5
mvn clean package
sha256sum target/*.jar

Jika SHA berbeda, set project.build.outputTimestamp, lalu ulangi.

Exercise 2 — Generated Source Audit

Cari generated source:

find . -type d \( -path '*generated-sources*' -o -path '*generated*' \) -print

Audit:

Generator apa?
Version dipin di mana?
Input contract di mana?
Output masuk source control atau target?
Ada timestamp/path lokal di output?

Exercise 3 — Release Metadata Bundle

Buat script yang menghasilkan:

target/release-metadata/
├── effective-pom.xml
├── effective-settings.xml
├── dependency-tree.txt
├── java-version.txt
├── maven-version.txt
├── git-commit.txt
├── artifact-checksums.txt
└── jar-entries.txt

Command dasar:

mkdir -p target/release-metadata
mvn help:effective-pom -Doutput=target/release-metadata/effective-pom.xml
mvn help:effective-settings -Doutput=target/release-metadata/effective-settings.xml
mvn dependency:tree -DoutputFile=target/release-metadata/dependency-tree.txt
java -version 2> target/release-metadata/java-version.txt
mvn -version > target/release-metadata/maven-version.txt
git rev-parse HEAD > target/release-metadata/git-commit.txt
sha256sum target/*.jar > target/release-metadata/artifact-checksums.txt
jar tf target/*.jar | sort > target/release-metadata/jar-entries.txt

27. Summary

Reproducible build adalah discipline, bukan satu plugin.

Maven memberi building blocks:

  • POM sebagai build declaration
  • dependencyManagement/BOM untuk graph control
  • pluginManagement untuk build logic control
  • project.build.outputTimestamp untuk archive timestamp control
  • repository settings/mirrors untuk resolution control
  • toolchains untuk JDK control
  • artifact plugin/reporting untuk diagnostics

Tapi engineer tetap harus menjaga invariant:

No hidden input.
No mutable release dependency.
No unpinned build logic.
No generated randomness.
No local-machine-specific artifact content.

Jika Part 027 mengajarkan cara menambahkan layout exception dengan benar, Part 028 mengajarkan konsekuensi akhirnya: setiap exception harus tetap menghasilkan artifact yang bisa dipercaya.

Di part berikutnya, kita akan masuk ke Supply Chain Security, SBOM, and Signing: checksums, signatures, GPG, SBOM, dependency audit, repository trust boundary, dan bagaimana Maven build menjadi bagian dari software supply chain security.

Lesson Recap

You just completed lesson 28 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.