Skip to main content

Welcome to a Grasp lesson

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

Lesson 31

Exceptions personnalisées pour erreurs métier

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons appris à prendre le contrôle du flux d'erreurs avec throw et throws. Nous avons même esquissé une première exception personnalisée, UserNotFoundException, pour signaler une erreur métier claire.

Aujourd'hui, nous allons formaliser et approfondir cette notion. L'objectif est de maîtriser la création de classes d'exceptions personnalisées pour représenter des erreurs métier spécifiques. Pourquoi ne pas se contenter de IllegalArgumentException ou RuntimeException ? Car en développement backend professionnel, des exceptions comme SoldeInsuffisantException ou ProduitHorsStockException rendent votre code plus lisible, plus maintenable et vos APIs beaucoup plus expressives. C'est une pratique fondamentale pour construire des systèmes robustes.

À la fin de cette leçon, vous saurez définir vos propres types d'erreurs pour refléter précisément la logique de votre application.

1. Pourquoi créer des exceptions personnalisées ?

Les exceptions intégrées à Java sont puissantes, mais souvent trop génériques. Si votre méthode retirerArgent() lance une IllegalArgumentException, l'appelant sait qu'un argument est invalide, mais il ne sait pas pourquoi. Le montant était-il négatif ? Ou le solde était-il simplement insuffisant ?

Créer vos propres exceptions résout ce problème et apporte plusieurs avantages clés :

  • Clarté et Sémantique : Le nom de la classe d'exception lui-même documente l'erreur. SoldeInsuffisantException est bien plus parlant que Exception.
  • Gestion d'erreurs ciblée : Cela vous permet d'écrire des blocs catch spécifiques pour chaque type d'erreur métier. Vous pouvez ainsi afficher un message spécifique à l'utilisateur pour une erreur de stock, tout en enregistrant une erreur de base de données dans un fichier de logs pour les administrateurs.
  • Contrat d'API robuste : Lorsque la signature d'une méthode de service inclut throws ProduitNonTrouveException, elle communique clairement aux autres développeurs une condition d'erreur possible qu'ils doivent gérer.

Pour approfondir cette idée, l'article suivant illustre parfaitement l'intérêt de créer des exceptions qui correspondent à la logique de votre application.

Gestion des Exceptions en Java : Guide Complet - DEV Community

Cet article de dev.to montre des exemples très concrets d'exceptions personnalisées pour des règles métier.

Lisez la section "5. Exceptions Personnalisées". Portez une attention particulière aux deux exemples : SoldeInsuffisantException pour une application bancaire. AgeInvalideException pour la validation d'un utilisateur. Observez comment, dans les deux cas, l'exception personnalisée permet de représenter une règle métier qui a été violée.


2. Comment créer une classe d'exception personnalisée

Créer une exception personnalisée en Java est étonnamment simple : il s'agit de créer une classe qui hérite d'une classe d'exception existante.

La structure de base est la suivante :

public class NomDeMonException extends Exception {
    // ... constructeurs ...
}

La décision la plus importante est de choisir de quelle classe hériter :

  • extends Exception : Crée une exception contrôlée (checked). Le compilateur forcera l'appelant à la gérer (avec try-catch ou throws). C'est le choix par défaut pour les erreurs métier prévisibles et récupérables (ex: "le panier est vide", "le coupon a expiré").
  • extends RuntimeException : Crée une exception non contrôlée (unchecked). L'appelant n'est pas obligé de la gérer. On l'utilise pour des erreurs de programmation ou des états anormaux qui ne devraient pas se produire dans un système fonctionnel (ex: une configuration essentielle est manquante).

Les constructeurs : le cœur de votre exception

Une fois la classe créée, l'étape la plus importante est de définir ses constructeurs. Ces derniers permettent de transmettre des informations utiles sur l'erreur. Ils appellent presque toujours le constructeur de la classe parente via le mot-clé super().

La vidéo suivante vous guidera pas à pas dans la création d'une exception personnalisée.

JAVA (Intermédiaire) - 75 - Créer sa propre exception en Java

La vidéo "JAVA (Intermédiaire) - 75 - Créer sa propre exception en Java" de la chaîne LES TEACHERS DU NET est un excellent tutoriel pratique.

Suivez les étapes présentées par l'instructeur : Bonne pratique : Notez l'idée d'organiser les exceptions dans un package dédié (ex: com.monprojet.exceptions). Création de la classe : Voyez comment la classe AgeInvalidException est créée et comment elle hérite de Exception. Ajout du constructeur : C'est la partie la plus importante. Observez comment un constructeur qui prend un String message est ajouté, et comment super(message) est utilisé pour passer ce message à la classe Exception parente. Lancement de l'exception : Enfin, voyez comment throw new AgeInvalidException(...) remplace une exception plus générique, rendant le code plus spécifique.

L'image ci-dessous résume la structure d'une classe d'exception personnalisée, montrant plusieurs constructeurs courants que vous pouvez inclure.

Cette image montre la définition de la classe `OutOfStockException`. On y voit l'héritage de `Exception`, et plusieurs constructeurs surchargés. Le plus courant est celui qui accepte un message. Celui qui accepte une `Throwable cause` est utile pour l'encapsulation d'exception (exception chaining), une technique plus avancée.

3. Bonnes pratiques

Lorsque vous concevez vos exceptions, gardez ces quelques règles en tête pour assurer la qualité de votre code.

Maîtriser la gestion des exceptions en Java : guide essentiel - Anaska

Cet article d'Anaska résume les bonnes pratiques de conception pour les exceptions personnalisées.

Lisez la sous-section "Bonnes pratiques pour exceptions personnalisées". Elle contient des recommandations clés.

Voici un résumé des points essentiels :

  • Nommez-les clairement : Le nom doit être descriptif et se terminer par Exception (ex: PaymentGatewayTimeoutException).
  • Fournissez des messages utiles : Le message d'erreur doit permettre à un développeur de comprendre le contexte de l'erreur sans avoir à lire tout le code source. Incluez des valeurs pertinentes (ex: "L'utilisateur avec l'ID '123' n'a pas été trouvé.").
  • N'utilisez pas les exceptions pour le flux de contrôle : Les exceptions doivent signaler des conditions exceptionnelles, pas être utilisées pour gérer la logique normale de l'application (comme une boucle for). Elles ont un coût en performance.
  • Préservez la hiérarchie : Si vous avez plusieurs exceptions liées, vous pouvez les faire hériter d'une exception de base commune. Par exemple, InvalidCardException et ExpiredCardException pourraient toutes deux hériter d'une exception de base PaymentException.

4. Exercice : Gestion de stock

Mettons en pratique ces concepts avec un scénario d'e-commerce. Vous allez créer un service qui gère l'achat de produits et qui doit lancer une exception si le stock est insuffisant.

Votre mission :

  1. Créer l'exception personnalisée :
    Créez une nouvelle classe InsufficientStockException qui hérite de Exception (ce sera donc une exception contrôlée).
    Elle doit avoir un constructeur qui accepte un String pour le message d'erreur.

    // Fichier InsufficientStockException.java
    public class InsufficientStockException extends Exception {
        public InsufficientStockException(String message) {
            super(message);
        }
    }
    
  2. Créer la couche de service :
    Créez une classe ProductService. À l'intérieur, simulez un stock avec un HashMap.
    Créez une méthode purchaseProduct(String productId, int quantity) qui :

    • Déclare qu'elle peut lancer InsufficientStockException.
    • Vérifie si la quantité demandée est supérieure au stock disponible.
    • Si c'est le cas, elle doit lancer new InsufficientStockException(...) avec un message clair.
    • Sinon, elle met à jour le stock et affiche un message de succès.
  3. Appeler le service :
    Dans votre méthode main, créez une instance de ProductService et appelez la méthode purchaseProduct dans un bloc try-catch pour gérer à la fois le cas de succès et le cas d'échec.

Cliquez ici pour voir la solution
import java.util.HashMap;
import java.util.Map;

// Fichier ProductService.java
class ProductService {
    private Map<String, Integer> stock = new HashMap<>();

    public ProductService() {
        // Initialisation du stock pour la simulation
        stock.put("PROD-001", 10);
        stock.put("PROD-002", 5);
    }

    public void purchaseProduct(String productId, int quantity) throws InsufficientStockException {
        Integer availableStock = stock.get(productId);

        if (availableStock == null) {
            // On pourrait créer une autre exception comme ProductNotFoundException
            throw new IllegalArgumentException("Produit avec l'ID " + productId + " n'existe pas.");
        }

        if (quantity > availableStock) {
            String errorMessage = "Stock insuffisant pour le produit " + productId 
                                + ". Demandé : " + quantity + ", Disponible : " + availableStock;
            throw new InsufficientStockException(errorMessage);
        }

        // Si le stock est suffisant, on met à jour
        stock.put(productId, availableStock - quantity);
        System.out.println("Achat réussi ! " + quantity + " unité(s) de " + productId + ". Nouveau stock : " + stock.get(productId));
    }
}

// Fichier Main.java
public class Main {
    public static void main(String[] args) {
        ProductService productService = new ProductService();

        // Cas 1 : Tentative d'achat avec stock insuffisant
        System.out.println("--- Tentative d'achat de 15 unités de PROD-001 ---");
        try {
            productService.purchaseProduct("PROD-001", 15);
        } catch (InsufficientStockException e) {
            System.err.println("Erreur métier capturée : " + e.getMessage());
        }

        System.out.println("\n--- Tentative d'achat avec stock suffisant ---");
        // Cas 2 : Achat réussi
        try {
            productService.purchaseProduct("PROD-002", 3);
        } catch (InsufficientStockException e) {
            System.err.println("Cette erreur ne devrait pas se produire : " + e.getMessage());
        }
    }
}

Résultat attendu :

--- Tentative d'achat de 15 unités de PROD-001 ---
Erreur métier capturée : Stock insuffisant pour le produit PROD-001. Demandé : 15, Disponible : 10

--- Tentative d'achat avec stock suffisant ---
Achat réussi ! 3 unité(s) de PROD-002. Nouveau stock : 2

Conclusion

Félicitations ! Vous savez maintenant comment enrichir votre gestion d'erreurs en créant des exceptions qui ont un sens métier. C'est une compétence qui distingue un code amateur d'un code professionnel, car elle améliore radicalement la maintenabilité et la clarté de vos applications.

Les points clés à retenir :

  • Les exceptions personnalisées permettent de représenter des erreurs métier spécifiques, rendant le code plus sémantique.
  • On les crée en définissant une classe qui hérite de Exception (checked) ou RuntimeException (unchecked).
  • Les constructeurs sont essentiels pour transmettre un message d'erreur clair et la cause originelle via super().
  • L'utilisation d'exceptions personnalisées permet une gestion d'erreurs ciblée avec des blocs catch spécifiques.

Maintenant que nous savons créer et gérer des erreurs de manière robuste, comment pouvons-nous nous assurer que notre code se comporte comme prévu, y compris lorsque ces erreurs se produisent ? Dans le prochain module, nous aborderons les tests unitaires avec JUnit. Vous apprendrez à écrire des tests automatisés qui non seulement valident le fonctionnement normal de votre code, mais vérifient aussi que vos exceptions personnalisées sont bien lancées au bon moment.