Grundlagen

Build und CI/CD

Vom Quellcode zur laufenden Anwendung, ohne dass jemand von Hand etwas tut: was Maven in welchen Phasen erledigt, wie Abhängigkeitskonflikte entstehen, was eine CI-Pipeline prüft – und wie man neue Versionen ausrollt, ohne dass es jemand merkt.

Lesestand

Ein Build verwandelt Quellcode in etwas Lauffähiges: kompilieren, testen, verpacken. Damit das auf jedem Rechner gleich abläuft, beschreibt man ihn als Code – in Java mit Maven oder Gradle. Die Werkzeuge laden auch die benötigten Bibliotheken samt deren Abhängigkeiten, und genau dort entstehen die tückischsten Fehler.

Continuous Integration heißt: Jede Änderung wird sofort automatisch gebaut und getestet. Continuous Delivery heißt: Jede erfolgreich getestete Version könnte per Knopfdruck live gehen; bei Continuous Deployment geht sie es automatisch. Eine Pipeline führt all das in festen Stufen aus – und mit Strategien wie Rolling, Blue-Green oder Canary merken Nutzer vom Ausrollen nichts.

Der Build als Code

Früher bestand ein Build oft aus einer Anleitung im Wiki und einem Entwickler, der „wusste, wie es geht“. Heute steht alles in einer Datei im Repository – bei Maven in der pom.xml. Maven folgt dabei einem festen Lebenszyklus: einer Kette von Phasen, an die Plugins ihre Arbeit hängen. Wer eine Phase aufruft, bekommt alle davor mit:

Was macht mvn …?

Maven hat zwei Stärken, die man leicht übersieht: Konvention statt Konfiguration – Quellen liegen in src/main/java, Tests in src/test/java, und niemand muss das einstellen. Und den Maven Wrapper (mvnw): ein kleines Skript im Repository, das genau die richtige Maven-Version herunterlädt. Damit baut jeder Rechner gleich, ohne Installation. Gradle verfolgt dieselben Ziele mit einer Programmiersprache statt XML und mit einem ausgefeilten Cache, der unveränderte Arbeitsschritte überspringt.

Abhängigkeiten und ihre Konflikte

Eine Spring-Boot-Anwendung deklariert vielleicht zwanzig Abhängigkeiten – und bekommt Hunderte. Jede Bibliothek bringt ihre eigenen mit, die transitiven Abhängigkeiten. Brauchen zwei davon dieselbe Bibliothek in verschiedenen Versionen, muss das Werkzeug sich für eine entscheiden. Auf dem Klassenpfad kann nur eine liegen.

Zwei Bibliotheken, zwei Versionen

Das ist der Grund für einen der rätselhaftesten Fehler im Java-Alltag: NoSuchMethodError oder ClassNotFoundException zur Laufzeit, obwohl alles kompiliert. Kompiliert wurde gegen eine Version, geladen wird eine andere. Abhilfe schaffen mvn dependency:tree zum Nachsehen, ausdrücklich festgelegte Versionen und BOMs („Bill of Materials“) – Versionslisten, die zusammenpassende Versionen vorgeben. Spring Boot liefert so eine für Hunderte Bibliotheken.

Reproduzierbare Builds

Ein Build ist reproduzierbar, wenn derselbe Quellcode immer exakt dieselben Bytes ergibt. Das klingt selbstverständlich, ist es aber nicht: Zeitstempel in JAR-Dateien, die Reihenfolge von Dateien, Versionsbereiche wie „1.+“, die heute etwas anderes auflösen als morgen. Reproduzierbarkeit ist wichtig, weil man nur dann prüfen kann, dass ein ausgeliefertes Programm wirklich aus dem behaupteten Quellcode stammt. Maven unterstützt sie mit der Eigenschaft project.build.outputTimestamp.

Continuous Integration

CI ist zuerst eine Arbeitsweise, dann ein Werkzeug: Alle integrieren ihre Änderungen mindestens täglich in den Hauptzweig, und jede Integration wird automatisch gebaut und getestet. Wer das nicht tut, erlebt die „Integrationshölle“ – wochenlang getrennte Branches, die beim Zusammenführen nicht mehr zusammenpassen. Das Werkzeug dafür ist eine Pipeline, etwa in GitHub Actions, GitLab CI oder Jenkins:

Eine Pipeline läuft

Die wichtigsten Prinzipien einer guten Pipeline:

  • Schnell scheitern: Billige Prüfungen zuerst. Wer zehn Minuten auf einen Integrationstest wartet, um dann zu erfahren, dass ein Komma fehlt, wartet beim nächsten Mal nicht mehr.
  • Einmal bauen, überall ausrollen: Dasselbe Artefakt – dieselbe JAR-Datei, dasselbe Container-Image – wandert von Test über Abnahme bis Produktion. Wer für jede Umgebung neu baut, testet etwas anderes, als er ausliefert.
  • Rot heißt: alles stehen lassen. Ein kaputter Hauptzweig hat Vorrang vor neuen Funktionen. Sonst gewöhnt sich das Team an rote Pipelines, und die Pipeline verliert jeden Wert.

Ausrollen ohne Ausfall

Die neue Version ist gebaut und getestet. Jetzt soll sie auf sechs Server – ohne dass ein Nutzer eine Fehlermeldung sieht. Drei gängige Strategien:

Version 2 ausrollen

Oft kommen Feature Flags dazu: Neuer Code wird ausgeliefert, aber per Schalter erst später – oder nur für einen Teil der Nutzer – aktiviert. Ausliefern und Freischalten werden damit zwei getrennte Entscheidungen, und ein Problem lässt sich mit einem Klick abschalten, ohne neu auszurollen.

Für Entwickler übersetzt

<!-- pom.xml: Versionen zentral festlegen -->
<dependencyManagement>
  <dependencies>
    <dependency>                                  <!-- BOM: passende Versionen für vieles -->
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${spring-boot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
# .github/workflows/build.yml – eine minimale CI für Maven
name: build
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: '21', cache: maven }
      - run: ./mvnw -B verify
BefehlWofür
./mvnw dependency:treeWelche Bibliothek zieht welche andere herein – und welche Version hat gewonnen?
./mvnw versions:display-dependency-updatesWelche Abhängigkeiten sind veraltet?
maven-enforcer-pluginBricht den Build ab, wenn Regeln verletzt sind – etwa widersprüchliche Versionen oder eine zu alte Java-Version.
-SNAPSHOT-VersionenVeränderliche Entwicklungsstände. Gehören nie in ein Release, denn morgen kann sich dahinter etwas anderes verbergen.
SBOMEine maschinenlesbare Liste aller enthaltenen Bibliotheken, etwa mit dem CycloneDX-Plugin. Damit weiß man sofort, ob eine neu bekannte Lücke einen betrifft.

Häufige Irrtümer

„CI heißt: Wir haben einen Jenkins.“ – CI heißt, häufig zu integrieren. Ein Build-Server, der langlebige Feature-Branches baut, ist keine Continuous Integration.
„Continuous Deployment heißt: Code ungeprüft live stellen.“ – Das Gegenteil: Weil es keine manuelle Freigabe gibt, müssen die automatischen Prüfungen so gut sein, dass man ihnen vertraut.
„Kompiliert, also passen die Abhängigkeiten.“ – Zur Laufzeit kann eine andere Version auf dem Klassenpfad liegen als zur Kompilierzeit. NoSuchMethodError ist das typische Zeichen.
„Für Produktion bauen wir neu, mit Produktions-Einstellungen.“ – Dann ist das, was läuft, nie getestet worden. Einstellungen gehören in Konfiguration oder Umgebungsvariablen, nicht ins Artefakt.
„Bei mir lokal geht's.“ – Genau dafür baut die Pipeline auf einer frischen Maschine: Dort fallen vergessene Dateien, lokale Einstellungen und Versionsunterschiede auf.

Verwandte Begriffe