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:
| Element | Bedeutung | Konvention |
|---|---|---|
groupId | Die Organisation oder der Namespace | Reverse-DNS, z. B. com.google.code.gson |
artifactId | Der Projektname innerhalb der Gruppe | Kleingeschrieben, mit Bindestrich, z. B. shop-api |
version | Das Release dieses Artefakts | Semantisch, z. B. 1.4.0; -SNAPSHOT für laufende Builds |
packaging | Der Ausgabetyp | jar (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.)
Was man aus dem Lauf mitnehmen kann:
- Die
modelVersionwird als4.0.0ausgegeben — 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.0heraus, das GAV-Tripel in seiner kanonischengroupId: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. packagingwurde alsjarzurückgelesen, der Standard-Ausgabetyp. Wäre espom, wäre dies ein Aggregator-/Parent-Projekt, das keine eigene JAR erzeugt.- Die Property
junit.versionwurde zu5.10.2aufgelö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
compilegesetzt (kein<scope>-Element, also in jedem Classpath), während JUnitscope=testzeigte, was es aus dem ausgelieferten Artefakt heraushält.