W3docs

Java Maven pom.xml

Das Maven Project Object Model – pom.xml-Struktur, Koordinaten, Properties und Vererbung verständlich erklärt.

Die pom.xml ist das Herzstück jedes Maven-Projekts. POM steht für Project Object Model — eine einzige XML-Datei, die deklariert, was dein Projekt ist (seine Identität), was es benötigt (seine Abhängigkeiten) und wie es gebaut wird (Plugins und Konfiguration). Maven liest diese Datei, lädt alles, was darin referenziert wird, aus einem Repository herunter und führt den Build aus. Während ein Ad-hoc-Projekt dieses Wissen über Shell-Skripte und einen lib/-Ordner mit manuell kopierten JARs verstreut, fasst Maven alles in einem einzigen deklarativen, versionskontrollierten Dokument zusammen.

Dieses Kapitel geht die POM-Struktur Schritt für Schritt durch: das minimale Grundgerüst, die GAV-Koordinaten, die jedes Artefakt benennen, wie Abhängigkeiten und Scopes funktionieren, Properties zur Vermeidung von Versions-Duplikaten und die Vererbung über ein Parent-POM. Wenn du neu bei Maven bist, beginne mit der Maven-Einführung; für die Build-Phasen, die das POM steuert, siehe den Maven-Build-Lifecycle.

Das minimale POM

Jedes POM ist ein XML-Dokument mit einem <project>-Element als Wurzel, das das Maven-4.0.0-Schema verwendet. Das kleinste sinnvolle POM deklariert seine Modellversion und seine Koordinaten — die vier Werte, die das Artefakt benennen:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.w3docs</groupId>
  <artifactId>shop-api</artifactId>
  <version>1.4.0</version>
  <packaging>jar</packaging>
</project>

<modelVersion> ist immer 4.0.0 — das ist die Version des POM-Formats, nicht die deines Codes. Lass es genau so, wie es angegeben ist.

Koordinaten: groupId, artifactId, version

Ein Maven-Artefakt wird durch seine GAV-Koordinaten identifiziert. Jede Abhängigkeit, die du je hinzufügst, wird durch dieselben drei (manchmal vier) Werte benannt. Es lohnt sich daher, sie genau zu kennen:

ElementBedeutungKonvention
groupIdDie Organisation oder der NamespaceReverse-DNS, z. B. com.google.code.gson
artifactIdDer Projektname innerhalb der GruppeKleingeschrieben, mit Bindestrich, z. B. shop-api
versionDas Release dieses ArtefaktsSemantisch, z. B. 1.4.0; -SNAPSHOT für laufende Builds
packagingDer Ausgabetypjar (Standard), war, pom

Zusammen ist groupId:artifactId:version global eindeutig. Damit findet Maven eine JAR in einem Repository und benennt das Artefakt, das dein Build erzeugt. Eine Version, die auf -SNAPSHOT endet (z. B. 1.5.0-SNAPSHOT), ist ein veränderbarer, in der Entwicklung befindlicher Build, den Maven erneut herunterladen kann; eine einfache Version wird als unveränderlich behandelt.

Abhängigkeiten deklarieren

Abhängigkeiten werden unter <dependencies> aufgelistet, jeweils als <dependency>-Block, der die Koordinaten einer benötigten Bibliothek angibt. Maven löst diese — und deren Abhängigkeiten, transitiv — aus einem Repository auf (standardmäßig Maven Central):

<dependencies>
  <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.11.0</version>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Der <scope> steuert, wann eine Abhängigkeit im Classpath ist. compile (der Standard) bedeutet überall; test bedeutet nur beim Kompilieren und Ausführen von Tests, sodass JUnit nie in deiner JAR enthalten ist. Weitere Scopes sind provided (vom Laufzeitsystem bereitgestellt, z. B. eine Servlet-API) und runtime (zum Ausführen, aber nicht zum Kompilieren benötigt, z. B. ein JDBC-Treiber). Transitive Auflösung, Versionskonflikte und Scope-Regeln werden ausführlich in Maven-Abhängigkeiten behandelt.

Properties: Versionen nicht wiederholen

Das Element <properties> definiert wiederverwendbare Variablen, die an anderen Stellen mit der ${name}-Syntax referenziert werden. Das Idiom ist, jede Bibliotheksversion einmal oben festzulegen, sodass Upgrades an einer einzigen Stelle erfolgen:

<properties>
  <maven.compiler.release>21</maven.compiler.release>
  <junit.version>5.10.2</junit.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>${junit.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies>

maven.compiler.release ist eine bekannte Property, die dem Compiler-Plugin mitteilt, auf welches Java-Release es abzielen soll — sauberer als die manuelle Konfiguration des Plugins. Wenn Maven das POM liest, interpoliert es jeden ${...}-Platzhalter und ersetzt ihn durch den Wert der Property, bevor irgendetwas aufgelöst wird.

Vererbung mit einem Parent-POM

POMs bilden eine Hierarchie. Ein <parent>-Element lässt ein Projekt Koordinaten, Properties und Abhängigkeits-Management von einem anderen POM erben. Das Starter-Parent von Spring Boot ist das klassische Beispiel — es legt einen konsistenten Satz von Bibliotheksversionen fest, sodass du <version> bei verwalteten Abhängigkeiten weglassen kannst:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>3.3.2</version>
</parent>

<dependencies>
  <dependency>
    <!-- version omitted: inherited from the parent's dependencyManagement -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

Ein Multi-Modul-Build verwendet denselben Mechanismus umgekehrt: Ein Aggregator-POM mit <packaging>pom</packaging> listet <modules> auf, und jedes Kind benennt es als <parent>. Gemeinsame Konfiguration befindet sich an einer Stelle; die Kinder bleiben klein.

Ein vollständiges Beispiel: Ein POM so lesen, wie Maven es tut

Eine pom.xml ist nur XML, daher können wir sie mit der im JDK integrierten DOM-API parsen und ihre Struktur genau so durchlaufen, wie Maven es tut — indem wir die Koordinaten, die Properties und jede Abhängigkeit lesen und dabei einen ${...}-Platzhalter von Hand interpolieren. (Maven selbst ist hier kein Build-Tool im Classpath, aber dies zeigt das Modell, auf dem es operiert.)

java— editable, runs on the server

Was man aus dem Lauf mitnehmen kann:

  • Die modelVersion wird als 4.0.0 ausgegeben — die Konstante, die jedes POM trägt. Sie identifiziert das POM-Format, nicht dein Projekt, und Maven würde ein POM ablehnen, das sie weglässt.
  • Die Koordinaten kamen als com.w3docs:shop-api:1.4.0 heraus, das GAV-Tripel in seiner kanonischen groupId:artifactId:version-Form. Dieser einzelne String ist die Art, wie Maven sowohl das Artefakt, das dieser Build erzeugt, als auch jede Abhängigkeit, die er auflöst, benennt.
  • packaging wurde als jar zurückgelesen, der Standard-Ausgabetyp. Wäre es pom, wäre dies ein Aggregator-/Parent-Projekt, das keine eigene JAR erzeugt.
  • Die Property junit.version wurde zu 5.10.2 aufgelöst, und der ${junit.version}-Platzhalter der JUnit-Abhängigkeit wurde auf denselben Wert interpoliert — genau die Substitution, die Maven vornimmt, damit eine Version an einer Stelle deklariert und wiederverwendet wird.
  • Der Abhängigkeits-Durchlauf meldete zwei Abhängigkeiten und gab jede mit ihrem Scope aus: Gson wurde standardmäßig auf compile gesetzt (kein <scope>-Element, also in jedem Classpath), während JUnit scope=test zeigte, was es aus dem ausgelieferten Artefakt heraushält.

Übungen

Übung
Wozu dienen die drei Elemente groupId, artifactId und version in einer pom.xml zusammen?
Wozu dienen die drei Elemente groupId, artifactId und version in einer pom.xml zusammen?
Was this page helpful?