Layer 8Ehrlicher Tech-Support
SoftwareSchweregrad: Lästigjava.lang.NoClassDefFoundError

Maven: Build grün, NoClassDefFoundError zur Laufzeit

Kompilieren klappt, Tests laufen, beim Start fehlt die Klasse. Scope provided liegt im Compile- und Test-Classpath, aber nicht im Runtime-Classpath.

Aufwand:
ca. 20 Min.

Das Symptom

mvn clean package läuft ohne eine einzige Warnung durch, die Tests sind grün, das JAR liegt im Zielverzeichnis. Beim Start bricht die Anwendung mit java.lang.NoClassDefFoundError ab und nennt eine Klasse, die der Compiler eben noch problemlos aufgelöst hat.

Besonders verwirrend wird es, wenn genau dieselbe Anwendung in der Entwicklungsumgebung startet und nur außerhalb scheitert – oder umgekehrt: auf dem Anwendungsserver läuft und als eigenständiges JAR nicht.

Die „Layer 8“-Wahrheit

Maven führt nicht einen Classpath, sondern mehrere, und der Scope entscheidet, in welchen davon eine Abhängigkeit einsortiert wird. Für provided steht es unmissverständlich in der Dokumentation: hinzugefügt zum Classpath für Kompilierung und Test, aber nicht zum Runtime-Classpath. Der Build ist also nicht falsch grün – er prüft schlicht eine andere Umgebung als die, in der die Anwendung später läuft.

provided ist ein Versprechen, und zwar keines von Maven, sondern eines vom Entwickler an Maven: Diese Klassen bringt zur Laufzeit das JDK oder ein Container mit. Für die Servlet-API in einem Web-Container stimmt das. Für eine Bibliothek in einer eigenständig startenden Anwendung stimmt es nicht, und dann hält sich Maven korrekt an eine Zusage, die nie jemand einlösen wollte. Die häufigste Herkunft dieses Scopes ist übrigens kein Nachdenken, sondern Kopieren – aus einer Antwort, in der es um ein Web-Projekt ging.

Der leisere Fall ist die Transitivität, oder besser deren Fehlen. provided ist nicht transitiv, und in der Auflösungstabelle der Dokumentation ist die Spalte provided durchgehend leer: Was eine Bibliothek als provided deklariert, kommt im abhängigen Projekt in keinem Scope an. Wer eine fremde Bibliothek einbindet, erbt deren Erwartung an einen Container, ohne davon zu erfahren. Der Compiler schweigt, weil er die Klasse nie braucht, und die Lücke fällt erst beim ersten Aufruf im Betrieb auf – dort, wo es teuer ist.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Den Scope der fehlenden Klasse nachschlagen

    Die Meldung nennt den Klassennamen; daraus ergibt sich das Artefakt. Zuerst in der pom.xml nachsehen, mit welchem Scope es eingetragen ist. Steht dort provided oder test, ist die Ursache gefunden, bevor man irgendetwas ausprobiert hat. Der Abhängigkeitsbaum zeigt den Scope für jedes Artefakt mit an.

    mvn dependency:tree -Dincludes=com.example:meine-bibliothek
    
  2. 2.

    Den Abhängigkeitsbaum nach Scope filtern

    dependency:tree zeigt den vollständigen Abhängigkeitsbaum des Projekts. Mit dem Filter auf runtime sieht man genau das, was zur Laufzeit tatsächlich vorhanden ist – die Liste, die zählt, wenn die JVM startet.

    mvn dependency:tree -Dscope=runtime
    
  3. 3.

    Entscheiden, ob wirklich jemand liefert

    provided ist eine Zusage an Maven, dass das JDK oder ein Container die Abhängigkeit zur Laufzeit bereitstellt. Diese Frage ehrlich beantworten: Läuft die Anwendung in einem Web-Container, der die Servlet-API mitbringt? Oder startet sie eigenständig, sodass es diesen Lieferanten gar nicht gibt?

  4. 4.

    Den Scope korrigieren, wenn niemand liefert

    Startet die Anwendung eigenständig, gehört die Abhängigkeit in den Standard-Scope compile – Compile-Abhängigkeiten stehen in allen Classpaths des Projekts zur Verfügung. Braucht der Compiler sie dagegen gar nicht, sondern nur die laufende Anwendung, ist runtime die genauere Wahl.

    <dependency>
      <groupId>com.example</groupId>
      <artifactId>meine-bibliothek</artifactId>
      <version>1.4.2</version>
    </dependency>
    
  5. 5.

    Bei fremden Bibliotheken die Lücke selbst schließen

    Fehlt eine Klasse, die zu einer Abhängigkeit der Abhängigkeit gehört, hilft kein Scope-Wechsel im eigenen Projekt: provided ist nicht transitiv und wird deshalb nie weitergereicht. Die betroffene Abhängigkeit muss ausdrücklich in die eigene pom.xml, mit passender Version.

Häufige Fragen (FAQ)

?Wenn der Compiler die Klasse findet, warum findet die JVM sie nicht?

Weil Maven nicht einen Classpath führt, sondern mehrere. Eine Abhängigkeit mit Scope provided wird laut Dokumentation dem Classpath für Kompilierung und Test hinzugefügt, aber nicht dem Runtime-Classpath. Der Compiler sieht sie also, die laufende Anwendung nicht. Das ist kein Defekt, sondern genau die Zusage, die provided bedeutet: Zur Laufzeit liefert das jemand anderes.

?Wann ist provided richtig und wann falsch?

Richtig, wenn wirklich eine Laufzeitumgebung die Klassen mitbringt – das Standardbeispiel ist die Servlet-API, die der Web-Container bereitstellt. Falsch, sobald die Anwendung eigenständig startet, etwa als ausführbares JAR. Dann existiert der Container nicht, der liefern sollte, und niemand füllt die Lücke.

?Eine transitive Abhängigkeit fehlt plötzlich. Kann provided daran schuld sein?

Ja, und das ist der unauffälligere Fall. provided ist nicht transitiv. Eine Bibliothek, die eine Abhängigkeit als provided deklariert, reicht sie nicht an das eigene Projekt weiter – dort steht dann eine Lücke, die man selbst füllen muss. In der Tabelle zur transitiven Auflösung ist die Spalte provided durchgehend leer.

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.

  1. [2]
    Apache Maven Dependency Plugin – dependency:tree

    maven.apache.org · abgerufen am

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

SWAnfängerfehlerError: Could not find or load main class

Java: Could not find or load main class

java findet die Startklasse nicht, obwohl die .class-Datei daneben liegt. Gefragt sind voll qualifizierter Klassenname und passender Klassenpfad.

SWLästigCould not resolve dependencies

Maven: Could not resolve dependencies

Maven findet eine Abhängigkeit nicht? Meist fehlt das Repository, ein Proxy blockt, oder ein halb geladenes Paket verstopft den Cache.