CodeGym /Cours /JAVA 25 SELF /Création d’exceptions personnalisées

Création d’exceptions personnalisées

JAVA 25 SELF
Niveau 24 , Leçon 1
Disponible

1. Introduction

La bibliothèque standard de Java comporte déjà de nombreuses exceptions : NullPointerException, IllegalArgumentException, IOException, etc. Mais il arrive que ces exceptions standard ne suffisent pas à décrire clairement l’erreur survenue dans votre programme.

Exemple concret :
Vous développez une application bancaire. L’utilisateur essaie de retirer plus d’argent qu’il n’y en a sur le compte. On peut lever IllegalArgumentException. Mais ce sera bien plus explicite si vous proposez votre propre exception, par exemple InsufficientFundsException. Le code indique alors immédiatement ce qui s’est passé.

Les exceptions personnalisées sont une personnalisation pertinente de l’application. Elles permettent d’affiner la gestion des problèmes et, grâce à leur nom (s’il est bien choisi !), elles indiquent immédiatement ce qui s’est passé. De plus, elles favorisent l’auto-documentation : des méthodes dont la signature contient throws MyException indiquent d’emblée quelles erreurs peuvent survenir. Enfin, vous pouvez toujours ajouter des champs supplémentaires (par exemple, le solde, le montant de l’opération, etc.).

2. Comment créer votre propre exception ?

C’est très simple : créez une nouvelle classe qui hérite de l’une des classes d’exception standard.

  • Pour les exceptions vérifiées (checked) : héritez de Exception.
  • Pour les exceptions non vérifiées (unchecked) : héritez de RuntimeException.

Exemple : exception vérifiée

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // On transmet le message à la classe parente
    }
}

Vous pouvez maintenant lever cette exception dans votre code :

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("Identifiant ou mot de passe incorrect");
}

Exemple : exception non vérifiée

public class NegativeBalanceException extends RuntimeException {
    public NegativeBalanceException(String message) {
        super(message);
    }
}

Quand utiliser checked et quand unchecked ?

  • Checked : si l’erreur est attendue et peut être gérée (par exemple, erreur de validation, fichier manquant, données utilisateur incorrectes).
  • Unchecked : si l’erreur est liée à un bug dans la logique du programme (par exemple, division par zéro, violation d’un invariant).

3. Constructeurs : rendre l’exception informative

En général, on implémente au moins un constructeur avec le paramètre String message. Mais on en ajoute souvent d’autres :

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException() {
        super();
    }
    public ScoreLimitExceededException(String message) {
        super(message);
    }
    public ScoreLimitExceededException(String message, Throwable cause) {
        super(message, cause);
    }
    public ScoreLimitExceededException(Throwable cause) {
        super(cause);
    }
}

Explication :

  • message — description textuelle de l’erreur.
  • cause — la cause (une autre exception), si vous souhaitez « envelopper » une erreur dans une autre.

Conseil : si vous ne savez pas quels constructeurs sont nécessaires — ajoutez au moins celui qui accepte une chaîne de caractères.

4. Utilisation des exceptions personnalisées dans le code

Prenons un exemple : nous avons un utilisateur, il a des points, et on ne peut pas en ajouter plus de 100.

public class User {
    private String name;
    private int score;

    public User(String name) {
        this.name = name;
        this.score = 0;
    }

    public void addScore(int points) throws ScoreLimitExceededException {
        if (score + points > 100) {
            throw new ScoreLimitExceededException("Limite de points dépassée ! Tentative d'ajout : " + points);
        }
        this.score += points;
    }
}

Classe d’exception :

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException(String message) {
        super(message);
    }
}

Traitement :

try {
    user.addScore(60);
    user.addScore(50); // Une exception sera lancée ici !
} catch (ScoreLimitExceededException e) {
    System.out.println("Erreur: " + e.getMessage());
}

Résultat :

Erreur: Limite de points dépassée ! Tentative d'ajout : 50

Vous vous demandez peut-être : et si, au lieu d’une exception, on utilisait simplement une condition if et, par exemple, on renvoyait false ou une autre valeur spéciale pour indiquer que l’opération a échoué ? Par exemple :

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // Ou lancer une RuntimeException si l’on ne veut pas gérer l’erreur
    }
    this.score += points;
    return true;
}

Bien que cette approche puisse paraître plus simple, elle présente des inconvénients lorsqu’il s’agit d’erreurs sérieuses ou de violations de la logique de l’application.

Premièrement, renvoyer false ou une autre valeur pour signaler une erreur oblige le code appelant à vérifier systématiquement la valeur de retour. Si le développeur l’oublie, l’erreur peut passer inaperçue, entraînant un comportement imprévisible du programme. Les exceptions, au contraire, forcent le traitement (pour les exceptions vérifiées) ou, à tout le moins, signalent explicitement un problème si elles ne sont pas interceptées.

Deuxièmement, les exceptions transmettent plus clairement la sémantique de l’erreur. Le retour de false peut signifier n’importe quoi : « échec », « non applicable », « indisponible ». L’exception ScoreLimitExceededException indique clairement et sans ambiguïté : « limite de points dépassée ». Cela améliore la lisibilité et la maintenabilité du code.

Troisièmement, les exceptions permettent de centraliser la gestion des erreurs. Au lieu d’éparpiller des vérifications if partout où addScore est appelée, vous pouvez intercepter l’exception en un seul endroit et prendre la décision appropriée : afficher un message à l’utilisateur, écrire dans les logs ou effectuer un rollback de transaction.

Enfin, pour des problèmes comme le dépassement d’une limite, il s’agit réellement d’une situation exceptionnelle (d’où le nom). Le flux d’exécution normal du programme suppose que les points seront ajoutés avec succès. Si ce n’est pas le cas — c’est une violation de la logique métier ou des invariants de l’objet, ce qui constitue un scénario idéal pour utiliser des exceptions.

5. Ajouter des champs personnalisés aux exceptions

Il est parfois utile d’ajouter à votre exception des données supplémentaires qui aideront à traiter l’erreur.

Exemple :

public class ScoreLimitExceededException extends Exception {
    private int currentScore;
    private int attemptedAdd;

    public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
        super(message);
        this.currentScore = currentScore;
        this.attemptedAdd = attemptedAdd;
    }

    public int getCurrentScore() { 
        return currentScore; 
    }
    public int getAttemptedAdd() { 
        return attemptedAdd; 
    }
}

Utilisation :

if (score + points > 100) {
    throw new ScoreLimitExceededException(
        "Limite de points dépassée !",
        this.score,
        points
    );
}

6. Points utiles

Comment nommer vos exceptions ?

En Java, il est d’usage de nommer les exceptions utilisateur avec le suffixe Exception : InvalidUserInputException, InsufficientFundsException, ScoreLimitExceededException.

N’appelez pas vos exceptions simplement Error ou Warning — cela peut induire en erreur d’autres développeurs (et même vous-même dans quelques semaines).

Où et quand lever vos propres exceptions ?

  • Lors de la validation des données utilisateur (par exemple, nom vide, âge négatif).
  • En cas de violation des règles métier (par exemple, dépassement de limite, tentative de retirer plus d’argent que le solde disponible).
  • En cas d’erreurs lors de l’appel de services externes (par exemple, service indisponible, délai dépassé).

7. Erreurs typiques lors de la création d’exceptions personnalisées

Erreur n° 1 : Mauvaise classe de base.
Héritez de Exception (ou de RuntimeException), et non de Throwable ou de Error.

Erreur n° 2 : Absence de constructeur avec message.
Sans constructeur acceptant une chaîne (String message), vos exceptions seront muettes et leur débogage sera difficile.

Erreur n° 3 : Utiliser des exceptions standard pour la logique métier.
Évitez de lever NullPointerException ou IllegalArgumentException là où une exception « parlante » spécifique à votre domaine est nécessaire.

Erreur n° 4 : Abus d’exceptions personnalisées.
Inutile de créer une classe d’exception distincte pour chaque broutille. Si l’erreur n’est pas unique à votre domaine, utilisez les exceptions standard.

Erreur n° 5 : Pas de sérialisation (rare, mais ça arrive).
Si votre exception doit être transmise sur le réseau ou être persistée, il convient d’implémenter implements Serializable. Cependant, pour des applications simples, ce n’est pas critique.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION