Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 30

Maîtriser `throw` et `throws` pour les exceptions.

Recommended resources

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.

Cette image montre le résultat concret du lancement d'une exception. Le code lance manuellement une `NullPointerException` avec `throw`. La sortie (OUTPUT) affiche la trace de la pile (stack trace), qui indique précisément où l'erreur a été déclenchée (`ligne 8`) et le chemin d'appel qui y a mené (`ligne 5`).

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 RuntimeException ou de Error.
    • 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.
  • Exceptions Contrôlées (Checked Exceptions)

    • Elles héritent de Exception mais pas de RuntimeException.
    • 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 :
      1. Les attraper avec un bloc try-catch.
      2. Déclarer que votre méthode peut les propager à l'appelant en utilisant le mot-clé throws dans la signature de la méthode.

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.

Cette image illustre parfaitement le concept d'exception contrôlée. Le code tente d'utiliser une méthode qui peut lancer une `IOException`. L'IDE signale une erreur de compilation ("Unhandled exception") et suggère d'ajouter `throws IOException` à la signature de la méthode `readData()`. C'est le compilateur qui vous force à prendre une décision concernant l'erreur.

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 :

  1. Créer l'exception :
    Créez une nouvelle classe UserNotFoundException. Pour l'instant, faites-la simplement hériter de Exception.

    public class UserNotFoundException extends Exception {
        public UserNotFoundException(String message) {
            super(message);
        }
    }
    
  2. Créer la couche de service :
    Créez une classe UserService avec une méthode findUserById. 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 de UserNotFoundException.

    // 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é.");
            }
        }
    }
    
  3. Appeler le service :
    Dans votre méthode main, créez une instance de UserService et appelez findUserById. 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".

  4. Gérer l'exception :
    Corrigez l'erreur en enveloppant l'appel dans un bloc try-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 throw est utilisée pour lancer manuellement une instance d'exception et interrompre le flux du programme.
  • Le mot-clé throws est 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-catch ou throws), contrairement aux exceptions non contrôlées (unchecked, RuntimeException).
  • Utiliser throws cré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.