W3docs

Java Classpath

Classpath beim Kompilieren und Ausführen von Java-Programmen setzen, damit die JVM Klassen und Abhängigkeiten findet.

Der Compiler weiß, in welchen Paketen er suchen soll, weil es den Classpath gibt — die Menge der Orte, an denen Java nach Class-Dateien sucht. Wenn Sie java MyApp ausführen und eine ClassNotFoundException erhalten, liegt die Ursache fast immer am Classpath: Die JVM konnte eine .class-Datei nicht dort finden, wo sie es erwartete. Wenn Sie verstehen, wie der Classpath aufgebaut ist, wird aus „es läuft einfach nicht" ein präzises, lösbares Problem.

Was der Classpath ist

Der Classpath ist eine geordnete Liste von Orten, in denen die JVM nach .class-Dateien sucht. Jeder Ort ist eines von:

  • Ein Verzeichnis — wird als Wurzel eines Paketbaums behandelt. Die JVM sucht darunter nach com/example/Foo.class.
  • Eine JAR-Datei — wird durchsucht, als wäre ihr interner Verzeichnisbaum ein Verzeichnis.
  • Ein Wildcard wie lib/* — trifft auf jede .jar-Datei in lib/ zu (nicht rekursiv und keine losen .class-Dateien).

Wenn Sie auf com.example.Foo verweisen, durchläuft die JVM den Classpath der Reihe nach und verwendet den ersten Treffer. Wenn zwei Orte dieselbe Klasse enthalten, gewinnt der frühere im Classpath — eine häufige Ursache für „Ich habe das JAR aktualisiert, aber der alte Code wird noch ausgeführt."

Den Classpath setzen

Es gibt drei Möglichkeiten, der JVM mitzuteilen, was sich im Classpath befindet, in der Reihenfolge der Präferenz:

# 1. -cp / -classpath flag (clearest, scoped to the one command):
java -cp out:lib/mylib.jar com.example.App

# 2. The CLASSPATH environment variable (set once, used by every invocation):
export CLASSPATH=out:lib/mylib.jar
java com.example.App

# 3. JAR manifest Class-Path entry (for executable JARs):
java -jar app.jar

-cp überschreibt CLASSPATH. Das globale Setzen der Umgebungsvariable ist eine häufige Fehlerquelle — ein veralteter CLASSPATH aus einem längst vergessenen Projekt verursacht mysteriöses Verhalten. Bevorzugen Sie -cp für jeden Befehl.

Unter Windows ist das Trennzeichen ;. Unter macOS und Linux ist es :. Setzen Sie den Wert in Anführungszeichen, wenn er Leerzeichen enthält.

Der Standard-Classpath

Wenn Sie keinen setzen, verwendet die JVM das aktuelle Verzeichnis (.) als Classpath. Deshalb funktioniert javac Hello.java && java Hello für Dateien im Standard-Paket ohne weitere Konfiguration — Hello.class liegt genau dort.

Sobald Sie Ihren Code in ein Paket packen, müssen Sie entweder vom richtigen Ort aus ausführen oder -cp explizit übergeben:

# Source: src/com/example/App.java with `package com.example;`
javac -d out src/com/example/App.java
java -cp out com.example.App      # must use the fully-qualified name

Ein häufiger Fehler ist java -cp out com/example/App. Das Argument für java ist ein Klassenname, kein Pfad — verwenden Sie Punkte, keine Schrägstriche.

Der Classpath zur Compile-Zeit

javac hat seinen eigenen Classpath, der sich von dem zur Laufzeit unterscheidet:

javac -cp lib/dependency.jar -d out $(find src -name "*.java")

-cp listet hier die Orte auf, an denen javac nach Typen sucht, auf die Ihre Quellen verweisen. Alles, was diese Quellen importieren, muss entweder in lib/dependency.jar oder auf dem impliziten Classpath vorhanden sein. Das -d-Flag des Compilers legt fest, wo die ausgegebenen .class-Dateien landen — typischerweise in einem parallelen out/-Baum.

Für die meisten Builds führen Sie javac und java nicht von Hand aus. Build-Tools — Maven, Gradle — stellen den Classpath aus deklarierten Abhängigkeiten zusammen. Der Sinn des manuellen Verständnisses ist, dass Sie debuggen können, was sie getan haben, wenn etwas schiefgeht.

JAR-Dateien im Classpath

Eine JAR-Datei ist eine ZIP-Datei mit Class-Dateien und Metadaten. Legen Sie eine in den Classpath, und die JVM behandelt ihren Inhalt als weiteren Paketbaum:

java -cp app.jar:lib/json.jar:lib/db.jar com.example.Main

Ein paar praktische Hinweise:

  • Wildcards expandieren nur zu JARs: -cp lib/* trifft auf jede .jar-Datei in lib/ zu, nicht auf Unterverzeichnisse oder lose .class-Dateien.
  • Wildcards sind keine Shell-Globs. Sie werden von der JVM selbst behandelt. Die meisten Shells würden lib/* zuerst expandieren; die JVM erwartet die wörtliche Zeichenkette lib/*. Setzen Sie sie in Anführungszeichen, um die Shell-Expansion zu verhindern: -cp "lib/*".
  • Die Reihenfolge ist bei Duplikaten entscheidend. Die erste JAR-Datei, die eine Klasse bereitstellt, gewinnt.

Ausführbare JARs

Wenn Sie eine Main-Class in der META-INF/MANIFEST.MF einer JAR-Datei festlegen, können Sie sie mit nur -jar ausführen:

java -jar app.jar

Zwei Besonderheiten bei -jar:

  • -cp wird ignoriert, wenn -jar verwendet wird. Die einzige Möglichkeit, Abhängigkeiten zum Classpath einer ausführbaren JAR hinzuzufügen, ist das Class-Path:-Attribut im Manifest, das andere JARs auflistet.
  • Die Main-Class der JAR ist Pflicht. Ohne sie weigert sich -jar zu starten.

Deshalb wurden Fat JARs — eine einzige JAR, die alle Abhängigkeiten enthält, erstellt mit dem Maven Shade Plugin oder dem Shadow Plugin von Gradle — zum Standard. Sie umgehen das gesamte Classpath-Problem bei ausführbaren JARs.

Classpath-Probleme debuggen

Zwei Fehler weisen direkt auf den Classpath hin, und sie bedeuten leicht unterschiedliche Dinge:

  • ClassNotFoundException — Code hat explizit eine Klasse über ihren Namen angefordert (oft via Class.forName(...) oder Reflection), und der Loader konnte sie nirgendwo im Classpath finden.
  • NoClassDefFoundError — die Klasse war beim Kompilieren vorhanden, fehlt aber zur Laufzeit oder kann nicht geladen werden. Die übliche Ursache ist eine Abhängigkeits-JAR, die im Compile-Classpath vorhanden, aber im Runtime-Classpath fehlt.

Wenn Sie auf einen der beiden Fehler stoßen, arbeiten Sie diese Checkliste durch:

  1. Drucken Sie den Classpath aus, den die JVM tatsächlich verwendet hatSystem.getProperty("java.class.path"), wie das folgende Beispiel zeigt. Der Classpath, den Sie zu übergeben glauben, und der tatsächlich wirksame sind oft unterschiedlich.
  2. Überprüfen Sie das Trennzeichen. : unter macOS/Linux, ; unter Windows. Ein falsches Trennzeichen führt stillschweigend dazu, dass zwei Einträge zu einem ungültigen Pfad zusammengeführt werden.
  3. Wildcards in Anführungszeichen. -cp "lib/*" — ein nicht in Anführungszeichen gesetztes lib/* wird von der Shell expandiert, bevor java es überhaupt sieht.
  4. Denken Sie daran, dass -jar -cp ignoriert. Wenn Sie mit -jar ausführen, wird der Classpath der Kommandozeile vollständig verworfen.

Der Modulpfad (ein kurzer Exkurs)

Seit Java 9 hat die JVM auch einen Modulpfad (-p oder --module-path), parallel zum Classpath. Module sind eine strengere, deklarationsbasierte Verpackungseinheit, die auf Paketen aufbaut. Die meisten Anwendungs-Codes laufen noch auf dem Classpath; Module sind am deutlichsten auf JDK-Ebene sichtbar. Sie können sie ignorieren, während Sie die Grundlagen lernen, und später darauf zurückkommen, wenn ein Framework Sie dazu auffordert.

Ein durchgearbeitetes Beispiel

Dieses Programm zeigt den Classpath von innen — was die JVM tatsächlich geladen hat und woher. Es verwendet nur java.lang, daher läuft es überall.

java— editable, runs on the server

java.class.path gibt den Anwendungs-Classpath aus; der Klassenlader von String gibt null aus, weil JDK-Kernklassen vom Bootstrap-Loader kommen, nicht von einem benutzersichtbaren Classpath-Eintrag. Die Klassenlader-Hierarchie ist der Motor, der den Classpath zum Laufen bringt — ihre Details sind ein Thema für ein fortgeschrittenes Kapitel.

Was als nächstes kommt

Damit sind Pakete und Importe abgeschlossen. Sie haben jetzt alle Bausteine — Benennung, Importieren, Deklarieren, Finden und die Standardbibliothek, die am anderen Ende jeder import-Zeile steht. Als nächstes kommt eine der prägenden Design-Entscheidungen von Java: geprüfte Ausnahmen und die try/catch/finally-Mechanismen zum Umgang mit Fehlern. Weiter zu Java-Ausnahmen.

Übungen

Übung
Ein Java-Programm läuft problemlos mit `java -cp out:lib/dep.jar com.example.App`, schlägt aber fehl mit `java -jar app.jar`, obwohl `app.jar` `Main-Class` in seinem Manifest hat und dieselbe `com.example.App`-Klasse bündelt. Warum?
Ein Java-Programm läuft problemlos mit `java -cp out:lib/dep.jar com.example.App`, schlägt aber fehl mit `java -jar app.jar`, obwohl `app.jar` `Main-Class` in seinem Manifest hat und dieselbe `com.example.App`-Klasse bündelt. Warum?
Was this page helpful?