Nach einem ganz normalen Dependency-Upgrade in Spring Boot
verabschiedete sich ein Service beim Deployment plötzlich mit der Meldung APPLICATION FAILED TO START und einer NoSuchMethodError aus der Bouncy Castle
-Bibliothek. Der Build lief zwar noch grün durch und auch die Tests bestanden problemlos, doch gekracht hat es, als die Anwendung lief.
Dahinter steckte ein klassischer Konflikt durch transitive Dependencies: Zwei Bibliotheken brachten dasselbe Bouncy-Castle-Artefakt in unterschiedlichen Versionen mit. Da Maven nach dem Prinzip „Nearest Wins“ auflöst, blieb schliesslich die falsche, nämlich die ältere, Version auf dem Classpath. Die Lösung? Eine einfache Zeile für die Dependency Exclusion.
Das Symptom: NoSuchMethodError zur Laufzeit beim Starten
Es liess sich alles wunderbar kompilieren, die Unit-Tests waren grün, das Artefakt wurde gebaut und das Container-Image schliesslich veröffentlicht. Doch beim Deployment blieb der Schritt auf einmal hängen und schlug fehl: Der Pod wurde einfach nicht ready und das Rollout lief in ein Timeout.
In den Anwendungs-Logs tauchte dann folgende Meldung auf:
An attempt was made to call a method that does not exist:
'void org.bouncycastle.util.BigIntegers.writeUnsignedByteArray(
java.io.OutputStream, java.math.BigInteger)'
The calling class, org.bouncycastle.bcpg.MPInteger,
was loaded from bcpg-jdk18on-1.84.jar
The called class, org.bouncycastle.util.BigIntegers,
was loaded from bcprov-jdk18on-1.80.2.jar
Eine NoSuchMethodError direkt beim Anwendungsstart – also kein Fehler beim Kompilieren, sondern ein handfester Linkage-Fehler zur Laufzeit.
Warum der Build den Fehler nicht bemerkt hat
Das Tückische an einer NoSuchMethodError ist ja, dass sie erst auftaucht, wenn die JVM versucht, die fehlende Methode zur Laufzeit aufzurufen. Und der fehlerhafte Aufruf steckte nicht einmal in unserem eigenen Code: Er passierte tief im Inneren von Bouncy Castle, wo das bcpg-Modul eine inkompatible Version von bcprov aufrief.
Genau deshalb lief der Build auch völlig ohne Warnungen durch. Der Compiler prüft schliesslich nur deinen eigenen Code gegen die Schnittstellen der eingebundenen Dependencies. Ob aber jede Bibliothek von Drittanbietern mit allen anderen Bibliotheken auf dem finalen Classpath harmoniert, kontrolliert er eben nicht. Maven hat die unpassenden Bouncy-Castle-Module also einfach zusammengepackt und weitergemacht.
Aus demselben Grund haben auch die Unit-Tests den Fehler übersehen: Keiner davon hat genau den Codepfad durchlaufen, der den fehlerhaften Aufruf auslöste. Wir haben das Problem also erst bemerkt, als die deployte Anwendung diesen Pfad beim Starten im Container zum ersten Mal ausführte.
Ursache: Inkompatible Versionen auf dem Maven-Classpath
Bouncy Castle wird in mehreren Modulen ausgeliefert, die im Grunde eine einzelne, aufgeteilte Bibliothek bilden:
bcprov-jdk18on: Der Provider – also die eigentliche Crypto-Engine..bcpg-jdk18on: Die OpenPGP-Schicht, die aufbcprovaufbaut. Sie bringt keine eigene Kryptographie mit, sondern greift direkt aufbcprovzu.
Diese beiden Module müssen zwingend in derselben Version vorliegen. bcpg 1.84 ruft nämlich eine Methode auf, die es erst seit bcprov 1.84 gibt.
Nach dem Dependency-Upgrade landeten jedoch bcpg, bcpkix und bcutil in der Version 1.84 auf dem Classpath – während bcprov auf Version 1.80.2 stehen blieb. Warum? Weil bcprov über zwei verschiedene Wege im Abhängigkeitsbaum eingebunden wurde, und zwar in unterschiedlichen Versionen:
service
├─ (feature library) ─▶ … ─▶ bcpg-jdk18on 1.84 ─▶ bcprov-jdk18on 1.84 (deep in the tree)
└─ spring-cloud-starter-openfeign ─▶ … ─▶ bcprov-jdk18on 1.80.2 (closer to the root)
Da Maven nur eine Version eines Artefakts auf dem Classpath behalten kann, wendet es die „Nearest Wins“-Strategie an: Die Version, die näher an der Wurzel des Abhängigkeitsbaums liegt, gewinnt. Da der Pfad über Spring Cloud OpenFeign kürzer war, behielt Maven bcprov 1.80.2 und verwarf die tiefer angeforderte Version 1.84.
Das Ergebnis: bcpg 1.84 lief zusammen mit bcprov 1.80.2 → die benötigte Methode fehlte → NoSuchMethodError, sobald der OpenPGP-Code beim Start zum ersten Mal ausgeführt wurde.
Wichtige Erkenntnis: Ein Versionskonflikt allein ist eben nicht automatisch kritisch. Zum echten Problem wurde er hier erst, weil die beiden Versionen Schnittstellen-inkompatibel waren – bcpg 1.84 setzt schliesslich eine Methode voraus, die erst mit bcprov 1.84 eingeführt wurde.
Die Lösung: Versionskonflikte von Bouncy Castle in der pom.xml beheben
Schliesse das ältere, störende bcprov-jdk18on einfach aus der Dependency aus, die es mitbringt. So sieht Maven den Kandidaten 1.80.2 gar nicht erst und löst bcprov sauber auf Version 1.84 auf, die zusammen mit bcpg mitkommt:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</exclusion>
</exclusions>
</dependency>
Eine genauso gute Alternative ist es, die Version explizit im dependencyManagement festzunageln:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.84</version>
</dependency>
</dependencies>
</dependencyManagement>
Wichtig: Wenn du bcprov aus OpenFeign ausschliesst, entfernst du es keineswegs aus der Anwendung. Es wird weiterhin benötigt und jetzt zusammen mit bcpg transitiv in der passenden Version 1.84 eingebunden. Die Exclusion eliminiert lediglich das ältere, konfliktbehaftete Duplikat, damit die richtige Version gewinnt. Sobald alle Bouncy-Castle-Artefakte einheitlich auf 1.84 ausgerichtet sind, verschwindet die NoSuchMethodError und dein Service startet wieder wie gewohnt.




