Le Classpath Java
Configurez le classpath lors de la compilation et de l'exécution de programmes Java pour que la JVM trouve vos classes et dépendances.
Le compilateur sait dans quels packages chercher grâce au classpath — l'ensemble des emplacements où Java recherche les fichiers de classes. Lorsque vous exécutez java MyApp et obtenez une ClassNotFoundException, la cause est presque toujours le classpath : la JVM n'a pas trouvé un fichier .class à l'endroit attendu. Comprendre comment le classpath est construit transforme « ça ne s'exécute tout simplement pas » en un problème précis et réparable.
Ce qu'est le classpath
Le classpath est une liste ordonnée d'emplacements que la JVM parcourt pour trouver les fichiers .class. Chaque emplacement est l'un des suivants :
- Un répertoire — considéré comme la racine d'une arborescence de packages. La JVM y cherche
com/example/Foo.class. - Un fichier JAR — parcouru comme si son arborescence interne était un répertoire.
- Un caractère générique comme
lib/*— correspond à chaque.jardanslib/(non récursivement, et sans les fichiers.classnon archivés).
Lorsque vous référencez com.example.Foo, la JVM parcourt le classpath dans l'ordre et utilise la première correspondance. Si deux emplacements contiennent la même classe, celui qui apparaît en premier dans le classpath l'emporte — cause fréquente du problème « j'ai mis à jour le JAR mais l'ancien code s'exécute encore ».
Définir le classpath
Il existe trois façons d'indiquer à la JVM ce qui figure dans le classpath, par ordre de préférence :
# 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 remplace CLASSPATH. Définir la variable d'environnement globalement est une source fréquente de bugs — un CLASSPATH obsolète d'un projet oublié depuis longtemps provoque des comportements mystérieux. Préférez -cp pour chaque commande.
Sur Windows, le séparateur est ;. Sur macOS et Linux, c'est :. Mettez la valeur entre guillemets si elle contient des espaces.
Le classpath par défaut
Si vous n'en définissez pas, la JVM utilise le répertoire courant (.) comme classpath. C'est pourquoi javac Hello.java && java Hello fonctionne immédiatement pour les fichiers sans package — Hello.class est juste là.
Dès que vous placez votre code dans un package, vous devez soit exécuter depuis le bon emplacement, soit passer -cp explicitement :
# 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 nameUne erreur courante est java -cp out com/example/App. L'argument passé à java est un nom de classe, pas un chemin — utilisez des points, pas des barres obliques.
Le classpath à la compilation
javac possède son propre classpath, distinct de celui utilisé à l'exécution :
javac -cp lib/dependency.jar -d out $(find src -name "*.java")-cp liste ici les emplacements où javac recherche les types référencés par vos sources. Tout ce que ces sources importent doit se trouver dans lib/dependency.jar ou sur le classpath implicite. L'option -d du compilateur indique où les fichiers .class générés seront déposés — généralement une arborescence parallèle out/.
Pour la plupart des projets, vous n'exécutez pas javac et java manuellement. Les outils de build — Maven, Gradle — assemblent le classpath à partir des dépendances déclarées. Comprendre cela manuellement vous permet de déboguer ce qu'ils ont fait quand quelque chose tourne mal.
Les fichiers JAR dans le classpath
Un JAR est un fichier ZIP contenant des fichiers de classes et des métadonnées. Placez-en un sur le classpath et la JVM traite son contenu comme une autre arborescence de packages :
java -cp app.jar:lib/json.jar:lib/db.jar com.example.MainQuelques remarques pratiques :
- Les caractères génériques n'étendent que les JARs :
-cp lib/*correspond à chaque.jardanslib/, pas aux sous-répertoires ni aux fichiers.classnon archivés. - Les caractères génériques ne sont pas des globs shell. Ils sont gérés par la JVM elle-même. La plupart des shells étendraient d'abord
lib/*; la JVM attend la chaîne littéralelib/*. Mettez-la entre guillemets pour éviter l'extension shell :-cp "lib/*". - L'ordre compte pour les doublons. Le premier JAR fournissant une classe l'emporte.
Les JARs exécutables
Si vous définissez un Main-Class dans le fichier META-INF/MANIFEST.MF d'un JAR, vous pouvez l'exécuter avec simplement -jar :
java -jar app.jarDeux pièges avec -jar :
-cpest ignoré lorsque-jarest utilisé. La seule façon d'ajouter des dépendances au classpath d'un JAR exécutable est via l'attributClass-Path:du manifeste, qui liste d'autres JARs.- Le
Main-Classdu JAR est obligatoire. Sans lui,-jarrefuse de s'exécuter.
C'est pourquoi les fat JARs — un seul JAR contenant toutes les dépendances, construit avec le plugin Maven Shade ou le plugin Shadow de Gradle — sont devenus la norme. Ils contournent toute la complexité du classpath des JARs exécutables.
Déboguer les problèmes de classpath
Deux erreurs pointent directement vers le classpath, et elles signifient des choses légèrement différentes :
ClassNotFoundException— le code a explicitement demandé une classe par son nom (souvent viaClass.forName(...)ou la réflexion) et le chargeur n'a pu la trouver nulle part dans le classpath.NoClassDefFoundError— la classe était présente lors de la compilation de votre code, mais elle est manquante ou non chargeable à l'exécution. La cause habituelle est un JAR de dépendance présent dans le classpath de compilation mais absent du classpath d'exécution.
Quand vous rencontrez l'une ou l'autre, passez en revue cette liste de vérification :
- Affichez le classpath effectivement utilisé par la JVM —
System.getProperty("java.class.path"), comme le montre l'exemple ci-dessous. L'ensemble que vous pensez avoir passé et celui en vigueur sont souvent différents. - Vérifiez le séparateur.
:sur macOS/Linux,;sur Windows. Un mauvais séparateur fusionne silencieusement deux entrées en un seul chemin invalide. - Mettez les caractères génériques entre guillemets.
-cp "lib/*"— unlib/*non mis entre guillemets est étendu par le shell avant quejavane le voie. - Rappelez-vous que
-jarignore-cp. Si vous exécutez avec-jar, le classpath de la ligne de commande est entièrement ignoré.
Le module path (un bref détour)
Depuis Java 9, la JVM dispose également d'un module path (-p ou --module-path), parallèle au classpath. Les modules sont une unité de packaging plus stricte, basée sur des déclarations, superposée aux packages. La plupart du code applicatif s'exécute toujours sur le classpath ; les modules sont surtout visibles au niveau du JDK. Vous pouvez les ignorer en apprenant les bases et y revenir lorsqu'un framework vous le demande.
Un exemple concret
Ce programme montre le classpath de l'intérieur — ce que la JVM a réellement chargé et d'où. Il n'utilise que java.lang, donc il s'exécute partout.
java.class.path rapporte le classpath de l'application ; le chargeur de classe de String s'affiche comme null car les classes du JDK central proviennent du chargeur bootstrap, et non d'une entrée de classpath visible par l'utilisateur. La hiérarchie des chargeurs de classes est le moteur qui fait fonctionner le classpath — ses détails font l'objet d'un chapitre avancé.
Et ensuite
Cela conclut les packages et les imports. Vous disposez maintenant de toutes les pièces — nommage, importation, déclaration, localisation, et la bibliothèque standard qui se trouve à l'autre bout de chaque ligne import. La prochaine étape est l'un des choix de conception fondamentaux de Java : les exceptions vérifiées et le mécanisme try/catch/finally utilisé pour gérer les erreurs. Continuez vers les exceptions Java.