Layer 8Ehrlicher Tech-Support

Maven: Gleicher Quellcode, jedes Mal eine andere JAR-Prüfsumme

Zwei Builds, ein Commit, zwei Hashes. Maven stempelt die Bauzeit in jeden Archiveintrag – project.build.outputTimestamp friert sie ein.

Aufwand:
ca. 30 Min.
Gilt für:
Maven 3, Maven 4

Das Symptom

Zweimal mvn clean package aus demselben Commit, zweimal sha256sum über das Ergebnis – und zwei verschiedene Prüfsummen. Kein Byte Quellcode hat sich geändert, kein Abhängigkeitsstand, nichts. Die JAR-Dateien sind fast gleich groß und verhalten sich identisch, aber signieren, vergleichen oder gegen ein Artefakt aus der CI prüfen lässt sich damit nichts.

Unangenehm wird das dort, wo jemand nachweisen soll, dass das ausgelieferte Artefakt wirklich aus dem angegebenen Quellstand stammt.

Die „Layer 8“-Wahrheit

Ein JAR ist ein ZIP-Archiv, und jeder Eintrag in einem ZIP-Archiv trägt einen Zeitstempel. Ohne anderslautende Vorgabe schreibt der Build dort die aktuelle Bauzeit hinein. Die Klassendateien selbst sind byteweise identisch – nur eben in einen Umschlag verpackt, auf dem steht, wann verpackt wurde. Die Prüfsumme bildet man aber über den Umschlag. Das ist die ganze Geschichte, und sie klingt banal, bis jemand eine Lieferkette absichern soll.

Die Gegenmaßnahme heißt project.build.outputTimestamp, eine Eigenschaft in der pom.xml, die den Zeitstempel der Archiveinträge auf einen festen Wert legt. Wer sie einträgt, in einem Zug zwei Builds macht und dann immer noch verschiedene Prüfsummen sieht, hat nichts falsch gemacht: Reproduzierbarkeit geschieht laut Apache auf Plugin-Ebene. Jedes am Bau beteiligte Plugin muss die Eigenschaft auswerten, und ältere Versionen tun das nicht. Für genau diese Diagnose gibt es mvn artifact:check-buildplan, das den Bauplan durchgeht und die Nachzügler benennt – der interessante Teil der Arbeit ist danach das Hochziehen von Plugin-Versionen, nicht das Setzen der Eigenschaft.

Die Sache hat ein Ablaufdatum, und das ist eine gute Nachricht. Ab Maven 4.0.0-beta-5 ist der Reproducible-Builds-Modus standardmäßig aktiv, ohne Änderung an der pom.xml. Bis der Umstieg vollzogen ist, bleibt es dabei: Wer Reproduzierbarkeit behauptet, ohne zweimal gebaut und die Hashes verglichen zu haben, behauptet nur etwas.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Den Unterschied überhaupt erst festnageln

    Vor der Konfiguration steht der Nachweis. Zweimal aus demselben Stand bauen und die Prüfsummen vergleichen – unterscheiden sie sich, weiß man, dass man ein Reproduzierbarkeitsproblem hat und nicht einen unsauberen Arbeitsbaum.

    mvn clean package && sha256sum target/*.jar
    mvn clean package && sha256sum target/*.jar
    
  2. 2.

    Den Ausgabe-Zeitstempel festschreiben

    Die Eigenschaft project.build.outputTimestamp legt den Zeitstempel der Archiveinträge fest, statt die Bauzeit zu nehmen. Ein fester Wert in ISO-8601-Form mit Z für UTC genügt; sinnvoll ist das Datum des letzten inhaltlichen Standes, nicht ein beliebiges.

    <properties>
      <project.build.outputTimestamp>2026-08-23T00:00:00Z</project.build.outputTimestamp>
    </properties>
    
  3. 3.

    Den Bauplan auf unreproduzierbare Plugins prüfen

    Die Eigenschaft allein genügt nicht, weil laut Apache alles auf Plugin-Ebene geschieht – ein Plugin, das sie nicht kennt, schreibt weiter die aktuelle Zeit. Das mitgelieferte Werkzeug geht den Bauplan durch und nennt genau die Plugins, deren Version nicht reicht.

    mvn artifact:check-buildplan
    
  4. 4.

    Die gemeldeten Plugins hochziehen

    Jedes vom Prüflauf beanstandete Plugin auf eine Version anheben, die reproduzierbar baut. Das ist der eigentliche Arbeitsanteil und der Grund, warum die Umstellung selten in fünf Minuten erledigt ist – die Versionen müssen einzeln in die pom.xml, festgenagelt und nicht geerbt.

  5. 5.

    Den Nachweis wiederholen

    Dieselbe Doppelmessung wie am Anfang. Erst wenn beide Läufe dieselbe Prüfsumme liefern, ist der Build reproduzierbar – die Konfiguration allein ist eine Absicht, keine Eigenschaft.

    mvn clean package && sha256sum target/*.jar
    mvn clean package && sha256sum target/*.jar
    

Häufige Fragen (FAQ)

?Der Quellcode ist identisch. Warum unterscheiden sich die JAR-Dateien trotzdem?

Weil ein JAR ein ZIP-Archiv ist und jeder Eintrag darin einen Zeitstempel trägt. Ohne Vorgabe schreibt Maven dort die aktuelle Bauzeit hinein. Die Klassendateien sind byteweise gleich, das Archiv drumherum ist es nicht – und die Prüfsumme wird über das Archiv gebildet, nicht über den Inhalt.

?Reicht es, die Eigenschaft in die pom.xml zu setzen?

Meistens nicht auf Anhieb. Reproduzierbarkeit passiert laut Apache auf Plugin-Ebene: Jedes beteiligte Plugin muss die Eigenschaft auch auswerten, und ältere Versionen tun das nicht. Genau dafür gibt es mvn artifact:check-buildplan, das den Bauplan durchgeht und die Plugins meldet, die noch aktualisiert werden müssen.

?Muss ich das in Maven 4 noch selbst konfigurieren?

Nein. Ab Maven 4.0.0-beta-5 ist der Reproducible-Builds-Modus laut Dokumentation standardmäßig aktiv, ohne Eingriff in die pom.xml. Für alles darunter bleibt die ausdrückliche Konfiguration nötig.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWAnfängerfehlerError: Could not find or load main class

Java: Could not find or load main class

java findet die Startklasse nicht, obwohl die .class-Datei daneben liegt. Gefragt sind voll qualifizierter Klassenname und passender Klassenpfad.

SWLästigCould not resolve dependencies

Maven: Could not resolve dependencies

Maven findet eine Abhängigkeit nicht? Meist fehlt das Repository, ein Proxy blockt, oder ein halb geladenes Paket verstopft den Cache.