Maven nimmt bei Konflikten die nächste, nicht die neueste
Zwei Bibliotheken wollen dieselbe Abhängigkeit in 1.0 und 2.0? Maven wählt die im Baum nächstgelegene. Bei gleicher Tiefe gewinnt die Reihenfolge.
Aufwand:
ca. 30 Min.
Voraussetzung:
Apache Maven, maven-dependency-plugin (Ziel dependency:tree), maven-enforcer-plugin (Regel dependencyConvergence), Die pom.xml des eigenen Projekts
Gilt für:
Apache Maven, Abhängigkeitsauflösung laut „Introduction to the Dependency Mechanism“ (abgerufen 2026-10-14), dependency:tree laut Plugin-Dokumentation maven-dependency-plugin 3.11.0, Regel dependencyConvergence laut Maven-Enforcer-Dokumentation (Beispiel mit Plugin-Version 3.6.3)
Das Symptom
Das Projekt kompiliert, die Tests laufen, und im Betrieb wirft die Anwendung einen
NoSuchMethodError oder NoClassDefFoundError. Die fehlende Methode gibt es aber –
in Version 2.0 einer Bibliothek, die eine der eigenen Abhängigkeiten ausdrücklich
verlangt. Im fertigen Paket liegt trotzdem Version 1.0. Noch rätselhafter: Nachdem
jemand zwei Einträge in der pom.xml umsortiert hat, ist der Fehler plötzlich weg –
oder taucht an einer ganz anderen Stelle auf.
Die „Layer 8“-Wahrheit
Hinter dem Fehler steckt eine Erwartung, die Maven nie versprochen hat: dass bei zwei
Versionen derselben Bibliothek die neuere gewinnt. Maven entscheidet nicht nach der
Versionsnummer, sondern nach der Entfernung. Gewählt wird die Version, die im
Abhängigkeitsbaum am nächsten am eigenen Projekt liegt. Bringt eine direkte
Abhängigkeit Version 1.0 mit und eine tiefer verschachtelte Version 2.0, landet 1.0 im
Paket – auch wenn eine andere Bibliothek ohne 2.0 nicht funktioniert. Kompilieren
klappt trotzdem, weil der eigene Code die fehlende Methode gar nicht aufruft; erst die
Bibliothek, die sie braucht, fällt zur Laufzeit darüber.
Liegen beide Versionen gleich tief, entscheidet die Reihenfolge der Deklaration. Das
erklärt das Umsortier-Phänomen, das sich sonst wie Aberglaube anfühlt: Die Reihenfolge
in der pom.xml ist bei gleichem Abstand Teil der Auflösungsregel, nicht Kosmetik.
Sichtbar wird das alles erst, wenn man hinsieht. Die normale Ausgabe von
dependency:tree zeigt nur den Sieger; die verdrängte Version taucht dort nicht auf, und
genau deshalb sucht man den Fehler so lange bei der Bibliothek, die 2.0 verlangt, oder bei
der JVM. Erst mit dem Parameter verbose nimmt das Ziel auch die weggelassenen Knoten
in die Ausgabe auf. Dann steht beides untereinander: welche Version gewonnen hat und
welche über welchen Pfad hereinkommen wollte und verloren hat.
Die Lösung ist ebenso unspektakulär wie die Regel. Wer eine bestimmte Version braucht,
schreibt sie in das eigene Projekt, am besten unter dependencyManagement, das bei
transitiven Abhängigkeiten Vorrang vor der Nähe hat. Und wer nicht jedes Mal erst im
Betrieb davon erfahren will, lässt die Enforcer-Regel dependencyConvergence
mitlaufen: Sie macht aus einem stillen Konflikt einen lauten Build-Fehler. Ein
Hinweis noch, weil er Zeit spart: Das Kurzbeispiel zu dependencyManagement auf der
Seite dieser Regel ist selbst fehlerhaft – das Element dependencies fehlt, und
version wird mit dem Tag groupId geschlossen. Belegt heißt nicht unbesehen
abgeschrieben; die richtige Struktur steht im Leitfaden zum Abhängigkeitsmechanismus.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.
Den Baum ansehen, einschließlich der verdrängten Versionen
dependency:tree zeigt, welche Version im Projekt gelandet ist. Mit dem Parameter verbose nimmt das Ziel laut Plugin-Dokumentation auch die weggelassenen Knoten in die Ausgabe auf („Whether to include omitted nodes in the serialized dependency tree“, Voreinstellung false). Mit includes lässt sich die Ausgabe auf ein Artefakt eingrenzen, Muster groupId:artifactId.
Maven erklärt die Regel an einem eigenen Beispiel: Bei A → B → C → D 2.0 und A → E → D 1.0 wird beim Bau von A die Version D 1.0 verwendet, „because the path from A to D through E is shorter“. Im eigenen Baum also abzählen, über wie viele Stufen jede Version hereinkommt. Die kürzere gewinnt, auch wenn sie älter ist; bei gleicher Länge gewinnt die zuerst deklarierte.
3.
Die gewünschte Version in dependencyManagement festlegen
In der eigenen pom.xml die benötigte Version unter dependencyManagement eintragen – mit dem Element dependencies dazwischen, so wie es der Maven-Leitfaden zeigt. Damit gilt diese Version für das Artefakt überall dort, wo es transitiv auftaucht. Alternativ die Abhängigkeit direkt deklarieren; dann ist sie per Definition die nächstgelegene. Danach dependency:tree erneut laufen lassen und prüfen.
Die Enforcer-Regel dependencyConvergence verlangt, dass die Versionen im ganzen Baum gleich sind, und bricht sonst den Build ab: Sie „will fail the build if A depends on one version of C and B depends on a different version of C“. So fällt ein Konflikt beim Bauen auf statt als NoSuchMethodError im Betrieb. Achtung beim Abschreiben: Das Kurzbeispiel zu dependencyManagement auf der Enforcer-Seite lässt das Element dependencies weg und schließt version mit einem falschen Tag – die Struktur aus dem Maven-Leitfaden ist die richtige.
Was der Code direkt benutzt, auch direkt deklarieren
Maven nennt es selbst eine gute Praxis, die Abhängigkeiten, die der eigene Quelltext direkt verwendet, ausdrücklich anzugeben, statt sich auf transitive Lieferungen zu verlassen – sonst kann der Build brechen, wenn eine Bibliothek ihre eigenen Abhängigkeiten ändert. Nebenbei macht eine direkte Deklaration die Version zur nächstgelegenen und damit zur gewählten.
Häufige Fragen (FAQ)
?Warum nimmt Maven nicht einfach die neuere Version?
Weil die Regel anders lautet. Unter „Dependency mediation“ schreibt Maven: „Maven picks the “nearest definition”. That is, it uses the version of the closest dependency to your project in the tree of dependencies.“ Entscheidend ist also der Abstand im Abhängigkeitsbaum, nicht die Versionsnummer. Eine ältere Version, die über einen kürzeren Pfad hereinkommt, schlägt eine neuere aus größerer Tiefe.
?Ich habe nur zwei Einträge in der pom.xml vertauscht, und plötzlich läuft etwas anders. Wie kann das sein?
Weil bei gleicher Tiefe die Reihenfolge entscheidet. Die Maven-Dokumentation sagt: „Note that if two dependency versions are at the same depth in the dependency tree, the first declaration wins.“ Bringen zwei direkte Abhängigkeiten dieselbe transitive Bibliothek in verschiedenen Versionen mit, gewinnt die, deren Eintrag in der pom.xml weiter oben steht. Umsortieren ändert damit die gewählte Version.
?Wie erzwinge ich eine bestimmte Version?
Indem man sie im eigenen Projekt ausdrücklich festlegt. Maven: „You can always guarantee a version by declaring it explicitly in your project's POM.“ Das geht als direkte Abhängigkeit oder sauberer über dependencyManagement; laut Dokumentation hat dependencyManagement bei transitiven Abhängigkeiten Vorrang vor der Vermittlung nach Nähe („dependency management takes precedence over dependency mediation for transitive dependencies“).
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.