1. « Ça ne marche pas » — ce n'est pas un diagnostic
À ce stade, on dispose déjà de trois vrais points de contrôle : classes, jar et run. L'un montre si le code et les ressources se compilent, l'autre si l'artéfact est empaqueté, le troisième si l'application démarre. On peut en tirer la meilleure habitude du jour : commencer par comprendre à quel stade tout s'est cassé.
Quand on commence à compiler et lancer un projet via Gradle, à un moment, l'inévitable se produit : quelque chose casse. Et la première réaction est presque toujours la même : « Gradle est cassé », « Java est cassé », « je suis cassé ». Spoiler : en général c'est un endroit précis qui casse à un moment précis — on n'est juste pas encore habitué à distinguer ces moments.
Dans cette leçon, on apprend à se poser la bonne question : à quel stade est-ce que ça a cassé ? Ça accélère considérablement la réparation. Parce que tout réparer en vrac, c'est comme réparer un vélo en commençant par acheter un nouveau garage : beaucoup d'activité, mais les roues ne tournent toujours pas.
Trois stades du projet : compilation → build → exécution
En simplifiant à l'extrême, notre projet a trois « états ». D'abord, il existe sous forme de sources (code Java + ressources). Ensuite, il devient un résultat compilé (classes, ressources, jar). Puis il devient un processus actif (l'application en cours d'exécution).
Il ne faut pas se mélanger les pinceaux dans les termes : compile-time est le moment où le compilateur Java essaie de transformer ton .java en .class. Build-time est un stade plus large, où Gradle exécute les tâches de build (y compris la compilation, le traitement des ressources et l'empaquetage). Runtime est le moment où l'application a vraiment démarré et où ton code s'exécute.
Pour bien ancrer ça, mettons tout dans un tableau. Il est simple, mais très utile (et oui, ce genre de tableau fait économiser des heures dans la vraie vie).
| Stade | Ce qui se passe en substance | Comment on vérifie généralement | Où « réparer » |
|---|---|---|---|
| Compile-time | Le code Java est compilé en bytecode (.class) | ./gradlew classes | Dans le code Java : syntaxe, types, signatures |
| Build-time | Gradle exécute les tâches de build : compilation, ressources, empaquetage, configuration d'application | ./gradlew jar / |
Dans build.gradle.kts, dans la structure du projet, dans les ressources |
| Runtime | Le processus JVM démarre, main() est appelé, ton code s'exécute | ./gradlew run | Dans la logique de l'application et dans les ressources/config disponibles au lancement |
Et encore une pensée utile : les erreurs compile-time se manifestent presque toujours comme un échec de Gradle sur la tâche :compileJava. Techniquement, c'est « une erreur pendant le build », mais dans le fond c'est une erreur de compilation du code Java. C'est pourquoi on la traite séparément : pour que le cerveau ne dise pas « encore Gradle » quand le compilateur t'informe honnêtement qu'on a oublié un ;.
2. Compile-time : les erreurs de compilation Java
Les erreurs compile-time sont le type « le plus sympa » d'erreurs. Elles sont désagréables, mais honnêtes : l'application ne démarre même pas tant que tu n'as pas mis le code en ordre. Le compilateur ne peut pas « deviner » ton intention, donc il réagit strictement. Et c'est un avantage : tu apprends l'existence du problème tout de suite, et non 15 minutes plus tard, quand ça explose à l'exécution chez l'utilisateur (ou chez toi, mais après le café).
En général, les erreurs compile-time apparaissent à cause de la syntaxe, de l'incompatibilité de types, de mauvaises signatures de méthodes, d'imports manquants, d'une utilisation incorrecte de static, de mauvais arguments dans les appels. Important : ces erreurs ne dépendent pas de si tu lances depuis l'IDE ou via Gradle — le compilateur est le même, c'est juste que l'IDE signale souvent le problème plus tôt.
À quoi ressemble une erreur compile-time
Prenons l'exemple le plus banal : on a oublié un point-virgule. Le code est court, mais il casse déjà toute la phase de compilation.
package com.example.readlater;
public class ReadLaterApplication {
public static void main(String[] args) {
// Le compilateur va planter ici : il faut un point-virgule à la fin de l'instruction
System.out.println("ReadLater Starter") // <- on a oublié ;
}
}
Si tu lances :
./gradlew classes
Gradle atteindra :compileJava et s'arrêtera. Le message contiendra quelque chose comme `;` expected et indiquera le numéro de ligne. Le plus précieux ici : le numéro de ligne et la raison précise, pas le cri général « BUILD FAILED ».
Compile-time : « j'ai presque deviné le type »
Un cas plus réaliste : tu attends un nombre mais tu travailles avec une chaîne (ou l'inverse). Le compilateur ne joue pas à ces jeux-là.
package com.example.readlater;
public class ReadLaterApplication {
public static void main(String[] args) {
// "8080" entre guillemets est un String, pas un nombre
int port = "8080"; // compile-time: incompatible types
System.out.println(port);
}
}
Dans la tête ça ressemble parfois à « mais 8080 c'est un nombre, pourquoi… », mais pour Java, les guillemets signifient immédiatement String.
Localiser rapidement un problème compile-time
L'astuce du débutant qui devient inopinément une habitude professionnelle : vérifie la compilation via classes quand tu as besoin de savoir « le code se compile-t-il ou pas ».
./gradlew clean classes
Pourquoi c'est pratique ? Parce que classes est un objectif assez « étroit ». Il compile le Java et prépare les ressources, sans commencer à empaqueter le jar ni lancer l'application. Si le problème est précisément dans le code, tu le vois rapidement et sans bruit superflu.
3. Build-time : les erreurs de build Gradle
Les erreurs build-time sont tout ce qui casse pendant la compilation du projet comme artéfact, mais pas nécessairement dans le code Java. Ici, le coupable est le plus souvent les paramètres dans build.gradle.kts, la structure du projet, une mauvaise configuration des plugins, parfois les ressources, parfois simplement des fautes de frappe dans le DSL.
C'est le moment où quelqu'un écrit : « J'ai une erreur, mais le code est correct ! » — et a souvent raison. Parce que Gradle peut planter avant la compilation ou après, par exemple lors de l'empaquetage du jar ou en essayant de préparer le run.
Kotlin DSL : Gradle ne « comprend pas » le script
Imaginons que dans le bloc application tu as fait une faute de frappe dans le nom d'une propriété :
plugins {
// On connecte le support du lancement via `run` et la config `application { ... }`
application
}
application {
// Erreur intentionnelle dans l'exemple : la propriété mainClasss n'existe pas (faute de frappe)
mainClasss = "com.example.readlater.ReadLaterApplication"
}
Ce n'est pas du code Java, c'est build.gradle.kts. Si tu essaies d'exécuter ./gradlew run ou ./gradlew classes, Gradle peut planter dès la phase de configuration du projet, c'est-à-dire avant l'exécution des tâches. Par le fond, c'est une erreur build-time, parce que « l'instruction de build est cassée ».
Psychologiquement, c'est traître : tu n'as rien changé dans le Java, et le projet a arrêté de se compiler. Mais si tu te rappelles que build.gradle.kts est aussi du code (simplement du code pour le build), tout devient logique.
Le plugin n'est pas connecté, mais le bloc est déjà là
Autre cas classique : tu écris le bloc application { ... }, mais tu oublies de connecter le plugin application (ou tu l'as accidentellement supprimé). Alors Gradle ne sait pas ce qu'est ce bloc, et le build plante.
plugins {
// application // <- quelqu'un l'a commenté, et le fun a commencé
}
application {
// Ce bloc nécessite le plugin `application` connecté
mainClass = "com.example.readlater.ReadLaterApplication"
}
C'est aussi un problème build-time : la structure et la configuration du build ne correspondent pas à ce que tu demandes à Gradle de faire.
Après la compilation : ressources et empaquetage
Parfois le code Java se compile, mais le build plante quand même. Par exemple, si tu essaies de créer le jar et qu'il y a des problèmes bizarres avec des fichiers, ou que tu as modifié à la main quelque chose qu'il ne fallait pas toucher.
En pratique, tu verras ça comme ceci : :compileJava a réussi, mais ça a planté sur :processResources ou :jar. C'est un signal important : le code se compile, mais le système de build n'a pas pu construire l'artéfact.
Localiser rapidement un problème build-time
Le même principe de « commande minimale » s'applique ici : si tu t'intéresses précisément à l'archive, utilise jar.
./gradlew jar
Si classes passe mais que jar plante, tu as déjà réduit la zone de recherche. C'est bien plus agréable que de lancer build «au cas où» et d'obtenir des tonnes de sortie.
4. Erreurs runtime : quand l'application s'exécute déjà
Les erreurs runtime sont ce qui se passe après le démarrage de l'application, quand le code Java s'exécute déjà. Le compilateur était content, Gradle a tout compilé, run a lancé la JVM — et là ton application dit elle-même : « Oups ». En général c'est une exception (exception) ou un comportement incorrect (par exemple, elle affiche quelque chose d'inattendu).
Pour le débutant, c'est le type d'erreur le plus désagréable, parce qu'on a l'impression : « Mais ça s'est compilé ! Donc ça devrait fonctionner ! » Hélas, le build dit juste une chose : « le code est formellement correct ». Et un code correct peut tout à fait être incorrect dans le fond. Java est discipliné, mais pas encore télépathe.
L'exemple runtime le plus simple : NullPointerException
Faisons un exemple délibérément pédagogique : tu as créé une variable null et tu as essayé d'y accéder. Le code se compile parfaitement. Mais l'exécution plante.
package com.example.readlater;
public class ReadLaterApplication {
public static void main(String[] args) {
// La variable est explicitement null
String value = null;
// Tout appel de méthode/champ sur null provoquera une NullPointerException
System.out.println(value.length()); // runtime: NullPointerException
}
}
Lancement :
./gradlew run
Et tu verras une stack trace indiquant sur quelle ligne le crash s'est produit. C'est déjà du runtime, parce que la JVM exécutait vraiment ton code et est arrivée à l'endroit problématique.
Runtime et ressources : getResourceAsStream retourne null
C'est particulièrement important après la leçon sur src/main/resources. getResourceAsStream() semble amical, mais il a son caractère : si la ressource n'est pas trouvée, il retourne null plutôt que de lancer une exception. Et si tu commences immédiatement à lire depuis null, tu obtiens une NPE.
Voilà comment on peut « accidentellement » se provoquer un crash runtime, si le fichier banner.txt n'est pas là où tu le crois, ou si tu t'es trompé dans le nom.
package com.example.readlater;
import java.io.IOException;
import java.io.InputStream;
public class ReadLaterApplication {
public static void main(String[] args) throws IOException {
// Si la ressource n'est pas trouvée, in sera null
try (InputStream in = ReadLaterApplication.class.getResourceAsStream("/banner.txt")) {
// On plantera ici avec NPE si in == null
System.out.println(in.available()); // runtime: NPE si in == null
}
}
}
Une variante un peu plus mature, mais toujours très simple — vérifier null et planter avec un message clair, pour ne pas chercher « pourquoi NPE » mais voir tout de suite « ressource non trouvée ».
package com.example.readlater;
import java.io.InputStream;
public class ReadLaterApplication {
public static void main(String[] args) {
// On essaie de trouver la ressource dans le classpath
InputStream in = ReadLaterApplication.class.getResourceAsStream("/banner.txt");
// On vérifie explicitement "ressource non trouvée"
if (in == null) {
throw new IllegalStateException("Resource /banner.txt not found on classpath");
}
System.out.println("Banner found!"); // Banner found!
}
}
Note bien : c'est toujours une erreur runtime (si la ressource est absente), mais maintenant elle explique le problème dans un langage humain.
5. Diagnostic via classes, jar, run
Quand on a trois types d'erreurs, la question naturelle se pose : « OK, mais comment savoir rapidement quel type j'ai en ce moment ? » Ici, ce qui aide n'est pas de la magie, mais un peu de discipline : vérifier le projet avec des commandes étroites et regarder sur quelle tâche tout a planté.
Dans cette leçon, on s'appuie spécialement sur les tâches que tu connais déjà : classes, jar, run. Ce sont trois points de contrôle : « le code se compile », « l'artéfact se construit », « l'application démarre ».
Voici un diagramme simple qu'on peut garder en tête (ou même à côté du projet, le temps de s'y habituer) :
flowchart TD
A["On a lancé ./gradlew run et tout a planté"] --> B{"Ça plante déjà sur :compileJava ?"}
B -->|Oui| C["Compile-time : erreur dans le code Java"]
B -->|Non| D{"Ça plante sur la config Gradle / build.gradle.kts ?"}
D -->|Oui| E["Build-time : erreur dans le script/configuration de build"]
D -->|Non| F{"classes réussit, mais jar plante ?"}
F -->|Oui| G["Build-time : problème d'empaquetage/ressources/structure"]
F -->|Non| H["Runtime : l'application a démarré et planté dans ton code"]
Si tu n'as pas envie de dessiner des schémas, tu peux le formuler simplement : d'abord je m'assure que la compilation réussit (classes), ensuite que l'empaquetage réussit (jar), et seulement après je vérifie le lancement (run). Là où ça casse, c'est là que je creuse.
6. Trois mini-cas sur ReadLater Starter
Voilà maintenant le plus utile : pas juste de la théorie sur les erreurs, mais trois pannes typiques qui arrivent vraiment aux débutants, et comment les distinguer rapidement. Je les présente comme de mini-histoires, parce que le cerveau s'en souvient mieux ainsi.
Cas A : le projet ne se compile plus après une petite modification
Tu as ajouté une ligne dans ReadLaterApplication, et tout a planté. Tu lances ./gradlew run et tu vois que la tâche :compileJava a FAILED. C'est presque toujours du compile-time. Tu ne cherches pas le problème dans les ressources, tu ne touches pas build/, tu ne réinstalles pas Gradle (s'il te plaît, ne réinstalle pas Gradle). Tu ouvres le message du compilateur et tu corriges le code.
La cause la plus fréquente — une erreur de syntaxe banale ou des types incompatibles. Corrigé — et tout repart.
Cas B : on n'a pas touché le code Java, mais le build a « planté d'un coup »
Tu n'as rien changé dans le Java, mais tu as décidé de « légèrement ajuster build.gradle.kts ». Après ça, tout lancement plante, parfois même ./gradlew tasks se comporte bizarrement. C'est du build-time, dans sa forme la plus pure : le script de build est cassé.
Ici, une habitude simple aide : si tu as modifié build.gradle.kts et que tout a planté après, ne cherche pas le problème dans le code Java. Reviens à la modification précédente, vérifie où est la faute de frappe, et rappelle-toi que le Kotlin DSL est strict — il n'aime pas les noms « à peu près corrects ».
Cas C : run démarre, mais l'application plante avec une exception
C'est du runtime. Et là, il est important d'apprendre à faire deux choses : d'abord, lire la stack trace de haut en bas jusqu'à la première ligne de ton code, et ensuite, ne pas confondre la cause et l'endroit du crash.
Par exemple, si tu vois une NullPointerException dans la ligne où tu lis la ressource, la cause n'est presque jamais « Java est cassé », mais « ressource non trouvée / variable null ». Et ça, tu peux le corriger dans ton code : en vérifiant null, en utilisant le bon chemin vers la ressource, en mettant le fichier au bon endroit.
7. Lire la sortie de Gradle
La sortie de Gradle ressemble au premier abord à « du bruit de console style science-fiction ». Mais elle est tout à fait lisible si on regarde au bon endroit. Une habitude simple est importante : Gradle dit presque toujours quelle tâche a planté. Et la tâche est un indice sur le stade où le problème s'est produit.
Si tu vois quelque chose comme Task :compileJava FAILED, c'est une zone de recherche. Si Task :processResources FAILED — c'en est une autre. Si l'erreur s'est produite avant la liste des tâches, ça signifie souvent que Gradle n'a même pas pu configurer le projet, c'est-à-dire que le problème est dans build.gradle.kts.
Parfois, il est utile de lancer Gradle pour qu'il montre plus de contexte. Pas comme un « incantation magique », mais simplement comme un moyen d'avoir des détails si le message standard ne suffit pas.
./gradlew run --stacktrace
Ça ne fait pas de toi un « ingénieur build avancé ». C'est juste une demande : « montre-moi plus en détail où ça a planté ». Mais il est important de ne pas se noyer dans les détails. Cherche d'abord : quelle tâche, quelle raison, quel fichier et quelle ligne. Et ensuite, si besoin, regarde plus en profondeur.
Et encore un conseil : ne jamais réparer le problème dans le dossier build/. Si quelque chose est cassé, tu corriges les sources (src/main/java, src/main/resources) ou le script de build (build.gradle.kts). Le dossier build/ est comme de la vaisselle temporaire après la cuisine : on peut la laver (clean), mais ça ne sert à rien d'y « remettre des ingrédients ».
8. Erreurs typiques dans le diagnostic
Erreur n°1 : appeler n'importe quel problème « erreur Gradle ».
Il est très facile de tout mettre sur le dos de Gradle, parce que c'est lui qui affiche le message d'erreur. Mais Gradle montre souvent honnêtement que c'est le compilateur Java ou ton programme en runtime qui a planté. Si tu vois :compileJava FAILED, c'est le code Java qu'il faut corriger, pas « gradle ».
Erreur n°2 : essayer de traiter un problème runtime via clean.
clean est une tâche utile, mais elle ne guérit pas une NullPointerException et n'ajoute pas le fichier banner.txt dans le bon dossier. Parfois, clean aide à supprimer les déchets après des expériences ratées, mais c'est souvent une action inutile. Si l'application plante dans main(), nettoyer build/ ne transforme pas null en chaîne.
Erreur n°3 : toujours lancer build même quand on a besoin d'une vérification ciblée.
Quand en mode panique tu lances « la plus grande commande » (build), tu obtiens aussi la plus grande sortie, et la cause devient plus difficile à voir. Très souvent, classes suffit pour comprendre que le code ne se compile pas, ou jar pour voir que l'empaquetage casse, ou run pour attraper une exception runtime.
Erreur n°4 : réparer les ressources en éditant build/resources/main.
C'est l'un des scénarios les plus rageants : tu as trouvé le fichier dans build/resources/main, tu l'as modifié, tout a marché… puis ça a recassé après le prochain build. Parce que build/ est régénéré. La source de vérité, c'est src/main/resources. Tout le reste est un résultat temporaire.
Erreur n°5 : confondre « main class non trouvée » avec « ça ne se compile pas ».
Si Gradle ne peut pas lancer la mainClass, ça peut avoir l'air alarmant, mais ça ne signifie pas toujours que le code Java est cassé. Le code peut se compiler parfaitement, mais la mainClass dans build.gradle.kts est indiquée incorrectement : mauvais package ou mauvais nom. Souvent perçu comme du runtime, mais c'est dans le fond un problème de configuration de build.
GO TO FULL VERSION