Bonjour ! Dans la leçon précédente, nous avons vu comment simplifier radicalement la gestion des ressources avec le bloc try-with-resources. C'était la dernière pièce du puzzle pour gérer les erreurs qui surviennent.
Aujourd'hui, nous changeons de perspective. Plutôt que de simplement "subir" les exceptions générées par Java, nous allons apprendre à les créer et les lancer nous-mêmes. L'objectif de cette leçon est de maîtriser les mots-clés throw et throws. C'est une compétence fondamentale en développement backend pour construire des API et des couches de service robustes, capables de communiquer clairement des états d'erreur (par exemple, "utilisateur non trouvé" ou "solde insuffisant") à la partie du code qui les appelle.
1. Lancer une exception manuellement avec throw
Jusqu'à présent, les exceptions que nous avons vues (ArithmeticException, IOException) étaient générées par la JVM lorsque quelque chose d'inattendu se produisait. Cependant, il est souvent nécessaire de déclencher une erreur nous-mêmes pour signaler qu'une condition métier n'est pas remplie. C'est le rôle du mot-clé throw.
L'instruction throw permet de "lancer" une instance d'objet Exception. La syntaxe est simple :
// Lance une nouvelle exception de type RuntimeException
throw new RuntimeException("Ceci est un message d'erreur personnalisé.");
Lorsqu'une instruction throw est exécutée, le flux normal du programme s'interrompt immédiatement et la JVM recherche un bloc catch capable de gérer cette exception en remontant la pile d'appels.
La vidéo suivante illustre très bien ce concept avec un système de paiement simple.
APPRENDRE LE JAVA #13 - LES EXCEPTIONS
Dans la vidéo "APPRENDRE LE JAVA #13 - LES EXCEPTIONS" de Graven - Développement, l'auteur montre comment déclencher une erreur si un utilisateur n'a pas assez d'argent pour un achat.
Regardez la section création d'une exception. Observez comment une condition if est utilisée pour valider une règle métier (le solde ne doit pas devenir négatif). Si la règle est violée, throw new Exception() est utilisé pour arrêter l'opération et signaler une erreur. C'est bien plus propre que de laisser le programme continuer avec une valeur de solde incorrecte.
2. Déclarer la propagation avec throws : Exceptions Contrôlées vs. Non Contrôlées
Vous avez peut-être remarqué dans la vidéo précédente que l'ajout de throw new Exception() provoquait une erreur de compilation. Pour la résoudre, l'IDE a proposé deux solutions : entourer le code d'un try-catch ou ajouter une déclaration throws à la méthode. Pourquoi ?
Cela nous amène à une distinction fondamentale en Java : les exceptions contrôlées (checked) et non contrôlées (unchecked).
-
Exceptions Non Contrôlées (Unchecked Exceptions)
- Elles héritent de
RuntimeExceptionou deError. - Exemples :
NullPointerException,ArrayIndexOutOfBoundsException,ArithmeticException. - Elles représentent généralement des erreurs de programmation (un bug) ou des erreurs système irrécupérables. Le compilateur ne vous oblige pas à les gérer. L'idée est que vous devriez corriger votre code pour qu'elles ne se produisent pas.
- Elles héritent de
-
Exceptions Contrôlées (Checked Exceptions)
- Elles héritent de
Exceptionmais pas deRuntimeException. - Exemples :
IOException,SQLException. - Elles représentent des conditions d'erreur prévisibles et souvent récupérables (ex: un fichier n'existe pas, la connexion réseau est perdue).
- Le compilateur vous oblige à les gérer. Vous avez deux options :
- Les attraper avec un bloc
try-catch. - Déclarer que votre méthode peut les propager à l'appelant en utilisant le mot-clé
throwsdans la signature de la méthode.
- Les attraper avec un bloc
- Elles héritent de
Le mot-clé throws (avec un 's') est une sorte de "contrat". Il prévient le code qui appelle votre méthode : "Attention, si vous m'utilisez, vous pourriez recevoir ce type d'exception. Soyez prêt à la gérer."
La vidéo ci-dessous explique cette différence cruciale.
TUTO Java : Introduction au mécanisme d'exceptions en Java
La vidéo "TUTO Java : Introduction au mécanisme d'exceptions en Java" par Dominique Liard est parfaite pour visualiser la différence de comportement imposée par le compilateur.
Regardez attentivement le passage de Checked vs. Unchecked. Notez d'abord comment le fait de lancer new RuntimeException() compile sans problème. Puis, observez comment le remplacement par new Exception() (qui est une exception contrôlée) génère une erreur de compilation. Enfin, voyez comment l'ajout de throws Exception à la signature de chaque méthode de la pile d'appels résout le problème. C'est le mécanisme de propagation.

3. La chaîne de responsabilité
Lorsque vous ajoutez throws MonException à une méthode methodeA, tout code qui appelle methodeA doit à son tour gérer MonException. S'il ne le fait pas avec un try-catch, il doit lui-même la déclarer dans sa signature, et ainsi de suite. L'exception remonte la pile d'appels jusqu'à ce qu'elle soit attrapée ou qu'elle atteigne le sommet de la pile (la méthode main), provoquant l'arrêt du programme.
C'est une "chaîne de responsabilité". Chaque méthode peut décider de traiter l'erreur ou de la déléguer à son supérieur.
Introduction à la gestion des exceptions - KooR.fr
Cet article du site Koor.fr fournit une excellente explication écrite des mots-clés throws et de la règle de compilation.
Commencez par lire la section "Le mot clé throws". Elle montre la syntaxe et un exemple concret où la méthode main délègue la gestion de IOException à la JVM. Lisez ensuite la section "Vous devez, normalement, dire ce que vous faites de vos exceptions". Elle réitère la règle imposée par le compilateur et explique pourquoi c'est une bonne chose pour la robustesse du code.
4. Exercice : Validation dans une couche de service
Mettons cela en pratique avec un scénario typique du développement backend. Nous allons créer un service qui recherche un utilisateur. Si l'utilisateur n'est pas trouvé, au lieu de retourner null (ce qui peut causer des NullPointerException chez l'appelant), nous allons lancer une exception contrôlée.
Votre mission :
-
Créer l'exception :
Créez une nouvelle classeUserNotFoundException. Pour l'instant, faites-la simplement hériter deException.public class UserNotFoundException extends Exception { public UserNotFoundException(String message) { super(message); } } -
Créer la couche de service :
Créez une classeUserServiceavec une méthodefindUserById. Cette méthode doit déclarer qu'elle peut lancer notre nouvelle exception. Si l'utilisateur avec l'ID donné n'est pas trouvé, elle doit lancer une instance deUserNotFoundException.// Fichier UserService.java public class UserService { // Simule une base de données d'utilisateurs private static final String[] USERS_DB = {"Alice", "Bob", "Charlie"}; // La méthode déclare qu'elle peut lancer une exception contrôlée public String findUserById(int id) throws UserNotFoundException { if (id >= 0 && id < USERS_DB.length) { return USERS_DB[id]; } else { // L'utilisateur n'est pas trouvé, on lance l'exception throw new UserNotFoundException("Utilisateur avec l'ID " + id + " non trouvé."); } } } -
Appeler le service :
Dans votre méthodemain, créez une instance deUserServiceet appelezfindUserById. Essayez avec un ID valide (ex: 1) et un ID invalide (ex: 99).// Fichier Main.java public class Main { public static void main(String[] args) { UserService userService = new UserService(); // Essayez d'appeler la méthode. Le compilateur va se plaindre ! // String user = userService.findUserById(99); // System.out.println("Utilisateur trouvé : " + user); } }Décommentez les lignes dans
main. Vous verrez une erreur de compilation : "Unhandled exception type UserNotFoundException". -
Gérer l'exception :
Corrigez l'erreur en enveloppant l'appel dans un bloctry-catch.
Cliquez ici pour voir la solution
// Fichier Main.java
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
try {
// Test avec un ID invalide
System.out.println("Recherche de l'utilisateur avec l'ID 99...");
String user = userService.findUserById(99);
System.out.println("Utilisateur trouvé : " + user);
} catch (UserNotFoundException e) {
// On attrape l'exception spécifique et on affiche un message clair.
System.err.println("Erreur métier : " + e.getMessage());
}
System.out.println("---");
try {
// Test avec un ID valide
System.out.println("Recherche de l'utilisateur avec l'ID 1...");
String user = userService.findUserById(1);
System.out.println("Utilisateur trouvé : " + user);
} catch (UserNotFoundException e) {
// Ce bloc ne devrait pas être atteint dans ce cas.
System.err.println("Erreur métier : " + e.getMessage());
}
}
}
Résultat attendu :
Recherche de l'utilisateur avec l'ID 99...
Erreur métier : Utilisateur avec l'ID 99 non trouvé.
---
Recherche de l'utilisateur avec l'ID 1...
Utilisateur trouvé : Bob
Cet exercice démontre comment les exceptions contrôlées forcent une gestion explicite des erreurs, rendant le code plus sûr et la communication entre les couches de votre application beaucoup plus claire.
Conclusion
Aujourd'hui, vous avez appris à passer du rôle de simple consommateur d'exceptions à celui de producteur. C'est une étape clé vers l'écriture d'un code de qualité professionnelle.
Les points clés à retenir :
- L'instruction
throwest utilisée pour lancer manuellement une instance d'exception et interrompre le flux du programme. - Le mot-clé
throwsest utilisé dans la signature d'une méthode pour déclarer qu'elle peut propager une ou plusieurs exceptions contrôlées. - Les exceptions contrôlées (checked) doivent obligatoirement être gérées (
try-catchouthrows), contrairement aux exceptions non contrôlées (unchecked,RuntimeException). - Utiliser
throwscrée une "chaîne de responsabilité" qui force les appelants à prendre conscience des erreurs potentielles et à les gérer.
Dans cet exercice, nous avons créé une ébauche d'exception personnalisée, UserNotFoundException. Dans la prochaine leçon, nous irons plus loin et verrons comment créer des classes d'exceptions personnalisées plus riches pour représenter précisément les erreurs de votre domaine métier, une pratique essentielle pour construire des applications backend maintenables et expressives.