W3docs

Java Classpath

Imposta il classpath per compilare ed eseguire programmi Java così che la JVM trovi le classi e le dipendenze.

Il compilatore sa in quali package cercare grazie al classpath — l'insieme delle posizioni in cui Java cerca i file di classe. Quando esegui java MyApp e ottieni una ClassNotFoundException, la causa è quasi sempre il classpath: la JVM non riusciva a trovare un file .class dove si aspettava di trovarlo. Capire come viene costruito il classpath trasforma un "non funziona e basta" in un problema preciso e risolvibile.

Cos'è il classpath

Il classpath è un elenco ordinato di posizioni in cui la JVM cerca i file .class. Ogni posizione è una delle seguenti:

  • Una directory — presa come radice di un albero di package. La JVM cerca com/example/Foo.class al suo interno.
  • Un file JAR — ricercato come se il suo albero di directory interno fosse una directory.
  • Un wildcard come lib/* — corrisponde a ogni .jar in lib/ (non in modo ricorsivo, e non a file .class sciolti).

Quando si fa riferimento a com.example.Foo, la JVM scorre il classpath in ordine e usa la prima corrispondenza. Se due posizioni contengono la stessa classe, quella precedente nel classpath vince — una causa comune del problema "ho aggiornato il JAR ma il vecchio codice è ancora in esecuzione".

Impostare il classpath

Ci sono tre modi per indicare alla JVM cosa si trova nel classpath, in ordine di preferenza:

# 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 sovrascrive CLASSPATH. Impostare la variabile d'ambiente a livello globale è una frequente fonte di bug — un CLASSPATH obsoleto di un progetto dimenticato da tempo causa comportamenti misteriosi. Preferisci -cp per ogni comando.

Su Windows, il separatore è ;. Su macOS e Linux è :. Metti tra virgolette il valore se contiene spazi.

Il classpath predefinito

Se non ne imposti uno, la JVM usa la directory corrente (.) come classpath. Ecco perché javac Hello.java && java Hello funziona subito per i file nel package predefinito — Hello.class si trova proprio lì.

Nel momento in cui inserisci il tuo codice in un package, devi eseguire dal posto giusto oppure passare -cp esplicitamente:

# 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

Un errore comune è java -cp out com/example/App. L'argomento di java è un nome di classe, non un percorso — usa i punti, non le barre.

Il classpath al momento della compilazione

javac ha il proprio classpath, distinto da quello in fase di esecuzione:

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

-cp qui elenca le posizioni in cui javac cerca i tipi a cui fanno riferimento i tuoi sorgenti. Tutto ciò che quei sorgenti importano deve trovarsi in lib/dependency.jar o nel classpath implicito. Il flag -d del compilatore indica dove vanno i file .class di output — tipicamente un albero out/ parallelo.

Per la maggior parte delle build non si eseguono javac e java a mano. I build tool — Maven, Gradle — assemblano il classpath dalle dipendenze dichiarate. Il valore di capirlo manualmente sta nel poter fare debug di quello che hanno fatto quando qualcosa va storto.

File JAR nel classpath

Un JAR è un file ZIP con file di classe e metadati. Inseriscine uno nel classpath e la JVM tratterà il suo contenuto come un altro albero di package:

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

Alcune note pratiche:

  • I wildcard si espandono solo ai JAR: -cp lib/* corrisponde a ogni .jar in lib/, non alle sottodirectory o ai file .class sciolti.
  • I wildcard non sono glob di shell. Vengono gestiti dalla JVM stessa. La maggior parte delle shell espanderebbe lib/* prima; la JVM si aspetta la stringa letterale lib/*. Mettila tra virgolette per impedire l'espansione della shell: -cp "lib/*".
  • L'ordine conta per i duplicati. Il primo JAR che fornisce una classe vince.

JAR eseguibili

Se imposti un Main-Class nel META-INF/MANIFEST.MF di un JAR, puoi eseguirlo semplicemente con -jar:

java -jar app.jar

Due avvertenze con -jar:

  • -cp viene ignorato quando si usa -jar. L'unico modo per aggiungere dipendenze al classpath di un JAR eseguibile è tramite l'attributo Class-Path: del manifest, elencando gli altri JAR.
  • Il Main-Class del JAR è obbligatorio. Senza di esso, -jar si rifiuta di avviarsi.

Ecco perché i fat JAR — un singolo JAR contenente tutte le dipendenze, costruito con il plugin Maven Shade o il plugin Shadow di Gradle — sono diventati uno standard. Evitano completamente il problema del classpath per i JAR eseguibili.

Fare debug dei problemi di classpath

Due errori puntano direttamente al classpath, e significano cose leggermente diverse:

  • ClassNotFoundException — il codice ha richiesto esplicitamente una classe per nome (spesso tramite Class.forName(...) o reflection) e il loader non riusciva a trovarla da nessuna parte nel classpath.
  • NoClassDefFoundError — la classe era presente quando il codice è stato compilato, ma è assente o non caricabile in fase di esecuzione. La causa solita è un JAR di dipendenza presente nel classpath di compilazione ma assente da quello di runtime.

Quando si incappa in uno dei due, segui questa checklist:

  1. Stampa il classpath effettivamente usato dalla JVMSystem.getProperty("java.class.path"), come fa l'esempio qui sotto. L'insieme che pensi di aver passato e quello in vigore sono spesso diversi.
  2. Controlla il separatore. : su macOS/Linux, ; su Windows. Un separatore sbagliato fonde silenziosamente due voci in un unico percorso errato.
  3. Metti i wildcard tra virgolette. -cp "lib/*" — un lib/* senza virgolette viene espanso dalla shell prima che java lo veda.
  4. Ricorda che -jar ignora -cp. Se esegui con -jar, il classpath dalla riga di comando viene scartato interamente.

Il module path (una breve digressione)

Da Java 9 la JVM dispone anche di un module path (-p o --module-path), parallelo al classpath. I moduli sono un'unità di packaging più rigorosa e basata su dichiarazioni, stratificata sopra i package. La maggior parte del codice applicativo gira ancora sul classpath; i moduli sono più visibili a livello di JDK. Puoi ignorarli mentre impari le basi e tornarci quando un framework te lo richiede.

Un esempio pratico

Questo programma mostra il classpath dall'interno — cosa ha effettivamente caricato la JVM e da dove. Usa solo java.lang, quindi gira ovunque.

java— editable, runs on the server

java.class.path riporta il classpath dell'applicazione; il class loader di String viene stampato come null perché le classi core del JDK provengono dal bootstrap loader, non da alcuna voce del classpath visibile all'utente. La gerarchia dei class loader è il motore che fa funzionare il classpath — i suoi dettagli sono argomento per un capitolo avanzato.

Cosa c'è dopo

Con questo si concludono i package e gli import. Hai ora tutti i pezzi — denominazione, importazione, dichiarazione, localizzazione e la libreria standard che si trova dall'altra parte di ogni riga import. Il prossimo argomento è una delle scelte di design fondamentali di Java: le eccezioni checked e il meccanismo try/catch/finally usato per gestire gli errori. Continua con eccezioni Java.

Esercizi

Pratica
Un programma Java funziona correttamente con `java -cp out:lib/dep.jar com.example.App`, ma fallisce con `java -jar app.jar` anche se `app.jar` ha `Main-Class` nel suo manifest e contiene la stessa classe `com.example.App`. Perché?
Un programma Java funziona correttamente con `java -cp out:lib/dep.jar com.example.App`, ma fallisce con `java -jar app.jar` anche se `app.jar` ha `Main-Class` nel suo manifest e contiene la stessa classe `com.example.App`. Perché?
Was this page helpful?