Aller au contenu

Gérer les erreurs dans Spring Boot : ce qu’il faut dire… et ce qu’il faut taire

Structurer ses erreurs HTTP ne suffit pas. Une API mature sait expliquer les erreurs métier, taire les détails techniques et rester volontairement floue sur la sécurité. Car bien gérer les erreurs, c’est surtout savoir ce que l’on choisit de révéler… et ce qu’il vaut mieux garder pour soi.

Gérer les erreurs dans Spring Boot : ce qu’il faut dire… et ce qu’il faut taire

Quand “bien structuré” ne suffit plus

Dans les articles précédents, nous avons appris à ne plus mentir avec les codes HTTP, puis à structurer nos erreurs à l’aide d’un format standard grâce à la RFC 9457 et ProblemDetail.

Nos réponses d’erreur sont désormais propres, cohérentes et compréhensibles.
Elles respectent les standards, parlent le bon langage HTTP et évitent les bricolages approximatifs.

Pourtant, un piège demeure : croire que toutes les erreurs méritent d’être exposées de la même façon.

Car une API ne se contente pas de décrire ce qui s’est mal passé.
Elle choisit ce qu’elle révèle, à qui et dans quel but.

Une erreur n’est jamais neutre :
C’est un message adressé, avec une intention.

Toutes les erreurs ne racontent pas la même histoire

Comprendre l’intention derrière l’erreur

Toutes les erreurs ne disent pas la même chose, parce qu’elles ne répondent pas au même besoin.
Avant même de penser format ou code HTTP, une question doit être posée :

Qui est censé agir après cette erreur ?

Prenons trois situations concrètes.

Erreur métier : le client peut agir

Hello hello = helloRepository.findById(id)
    .orElseThrow(() -> new HelloNotFoundException(id));

Ici, tout fonctionne correctement… sauf que la ressource demandée n’existe pas.

C’est une erreur :

  • Attendue,
  • Compréhensible,
  • Parfaitement légitime dans le cycle normal de l’application.

Surtout, c’est une erreur actionnable côté client :
Le consommateur peut changer l’identifiant, adapter son parcours ou afficher un message clair à l’utilisateur final.

La réponse peut donc être explicite :

{
  "title": "Hello introuvable",
  "status": 404,
  "detail": "Hello 42 non trouvé"
}

On ne révèle rien de sensible.
On décrit simplement la réalité fonctionnelle du système.

Erreur technique : le client n’y est pour rien

List<Hello> hellos = helloRepository.findAll();

Imaginons maintenant une indisponibilité de la base de données.

  • La requête du client est valide.
  • Le contrat est respecté.
  • Mais l’application est incapable de répondre.

Dans ce cas, exposer le message suivant serait une faute :

org.postgresql.util.PSQLException: Connection refused

Ce message :

  • N’aide pas le client,
  • Révèle l’infrastructure,
  • Transforme l’API en outil de debug public.

La réponse doit volontairement rester générique :

{
  "title": "Erreur interne",
  "status": 500,
  "detail": "Une erreur est survenue lors du traitement"
}

Le diagnostic précis appartient aux logs, aux métriques et aux traces.
Pas à la réponse HTTP.

Le client n’a rien à corriger.
Il doit simplement savoir que le problème ne vient pas de lui.

Erreur de sécurité : moins on en dit, mieux c’est

throw new AccessDeniedException("User does not have ROLE_ADMIN");

Dire au client exactement pourquoi l’accès est refusé revient à lui donner des indices :

  • Rôles existants,
  • Règles internes,
  • Surface d’attaque potentielle.

Dans ce contexte, le flou n’est pas une faiblesse.
C’est une mesure de protection.

{
  "title": "Accès refusé",
  "status": 403,
  "detail": "Vous n’êtes pas autorisé à effectuer cette action"
}

Le client comprend l’essentiel.
Le système reste discret.

Structurer ses exceptions pour garder le contrôle

La hiérarchisation comme outil de lisibilité

Pour garder la maîtrise de ce qui est exposé, encore faut-il maîtriser le type d’exceptions que l’on propage.

Dans une application Spring Boot, les exceptions métier héritent presque toujours de RuntimeException.
Ce choix n’est pas anodin : contrairement aux exceptions vérifiées (Exception), une RuntimeException peut remonter librement la pile d’appels sans imposer de try/catch ni polluer les signatures de méthodes.

Elle exprime un état invalide du domaine, pas un problème technique local à traiter immédiatement.

public abstract class BusinessException extends RuntimeException {
    protected BusinessException(String message) {
        super(message);
    }
}

public class HelloNotFoundException extends BusinessException {
    public HelloNotFoundException(Long id) {
        super("Hello " + id + " non trouvé");
    }
}

Cette hiérarchie n’est pas un luxe.
Elle apporte immédiatement :

  • Une lecture claire du type d’erreur,
  • Des handlers ciblés et explicites,
  • Un comportement homogène dans toute l’application.

Chaque exception raconte pourquoi elle existe.
Le reste de l’infrastructure peut alors décider comment elle sera racontée au client.

Pourquoi exposer une exception technique brute est une mauvaise idée

Le client n’est pas votre debugger

Un NullPointerException, une DataAccessException ou une IllegalStateException ne devraient jamais traverser la frontière HTTP.

@ExceptionHandler(DataAccessException.class)
public ProblemDetail handleDataAccess(DataAccessException ex) {
    log.error("Erreur base de données", ex);

    ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
    problem.setTitle("Erreur interne");
    problem.setDetail("Une erreur est survenue lors du traitement");

    return problem;
}

Ce schéma établit une séparation saine :

  • Le client reçoit une réponse stable et compréhensible,
  • L’équipe technique conserve l’intégralité du contexte.

L’API parle proprement.
Les logs parlent vrai.

Le filet de sécurité

Gérer l’inattendu sans paniquer

Même avec une hiérarchie bien pensée, certaines erreurs passeront entre les mailles du filet.

C’est normal.
Et c’est précisément pour cela qu’un handler global est indispensable.

@ExceptionHandler(Exception.class)
public ProblemDetail handleGenericException(Exception ex) {
    log.error("Erreur inattendue", ex);

    ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
    problem.setTitle("Erreur interne");
    problem.setDetail("Une erreur inattendue est survenue");

    return problem;
}

Ce handler garantit une chose essentielle :

Quelle que soit l’erreur, l’API reste cohérente.

Pas de stacktrace qui fuit.
Pas de réponse incohérente.
Pas de panique.

Enrichir ProblemDetail sans fuite d’information

Aider au diagnostic sans tout exposer

Il est possible d’aider le support et l’exploitation sans exposer de détails techniques.

ProblemDetail problem = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
problem.setTitle("Erreur interne");
problem.setDetail("Une erreur est survenue");
problem.setProperty("traceId", MDC.get("traceId"));
problem.setProperty("timestamp", Instant.now());

Le client peut fournir un identifiant précis lors d’un ticket de support.
L’équipe retrouve immédiatement la trace correspondante dans les logs.

Sans stacktrace.
Sans dépendance technique révélée.
Sans fuite d’information.

Conclusion

Ce que votre API fait est aussi important que ce qu’elle dit

Bien gérer les erreurs HTTP, ce n’est pas seulement une question de format ou de conformité aux standards.
C’est une question de discernement.

Tout dire est une facilité.
Trop dire est une faute.

Une API mature :

  • Parle clairement quand le client peut agir,
  • Se tait quand l’information devient dangereuse,
  • Protège ses entrailles techniques,
  • Et reste imperturbable, même face à l’imprévu.

Une bonne gestion des erreurs ne cherche pas à être transparente.
Elle cherche à être juste.

Savoir exposer une erreur est un savoir-faire.
Savoir en taire les détails est une preuve de maturité.

Et c’est souvent là que se reconnaissent les API conçues pour durer.

Il était une fois... Spring Boot
Salut, jeune explorateur du monde numérique ! Aujourd’hui, c’est moi, Spring Boot, qui viens te conter mon histoire, de mes débuts à aujourd’hui.
Gérer et superviser ses applications avec Spring Boot Admin
La supervision des applications est cruciale dans un monde numérique. Spring Boot Admin offre une solution puissante pour gérer vos services Spring Boot. Découvrez ses atouts, ses limites et comment l’implémenter efficacement.
Maîtrisez vos règles métier : Intégrez Drools dans Spring Boot
Découvrez comment intégrer Drools dans Spring Boot pour gérer vos règles métier avec flexibilité ! À travers un cas pratique bancaire, apprenez à configurer Drools, appliquer des règles dynamiques et booster la modularité de vos applications. Plongez dans ce guide complet pour devenir un pro !

Dernier