GevoxxInsights All articles
Software engineering · Java

Gérer proprement les erreurs dans Spring Boot : @ControllerAdvice et @ExceptionHandler

Centraliser le traitement des exceptions, renvoyer des réponses d'erreur cohérentes et lisibles, et éviter les try/catch partout. Le guide pratique avec du code.

Gérer proprement les erreurs dans Spring Boot : @ControllerAdvice et @ExceptionHandler

Dans une API Spring Boot, la gestion des erreurs mal pensée se traduit par des try/catch partout, des réponses incohérentes et des stack traces exposées au client. Spring propose un mécanisme élégant pour centraliser tout cela : @ControllerAdvice et @ExceptionHandler. Objectif : une seule source de vérité qui transforme chaque type d'exception en une réponse HTTP propre et cohérente.

Le principe : lever, ne pas attraper partout

La bonne pratique consiste à lever des exceptions métier claires dans le code, et à les traiter à un seul endroit. Le code métier reste lisible (pas de gestion d'erreur qui pollue la logique), et le format des réponses d'erreur est garanti uniforme.

// Exception métier explicite
public class RessourceIntrouvableException extends RuntimeException {
    public RessourceIntrouvableException(String msg) { super(msg); }
}

// Dans le service : on lève, on ne gère pas ici
Article a = repo.findById(id)
    .orElseThrow(() -> new RessourceIntrouvableException("Article " + id));

Le handler global avec @RestControllerAdvice

@RestControllerAdvice (= @ControllerAdvice + @ResponseBody) intercepte les exceptions levées par n'importe quel contrôleur. Chaque méthode @ExceptionHandler mappe un type d'exception vers une réponse.

@RestControllerAdvice
public class GestionnaireErreurs {

    @ExceptionHandler(RessourceIntrouvableException.class)
    public ResponseEntity<ErreurReponse> introuvable(RessourceIntrouvableException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErreurReponse("NOT_FOUND", e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErreurReponse> validation(MethodArgumentNotValidException e) {
        String details = e.getBindingResult().getFieldErrors().stream()
            .map(f -> f.getField() + " : " + f.getDefaultMessage())
            .collect(Collectors.joining(", "));
        return ResponseEntity.badRequest()
            .body(new ErreurReponse("VALIDATION", details));
    }

    @ExceptionHandler(Exception.class) // filet de sécurité
    public ResponseEntity<ErreurReponse> inattendu(Exception e) {
        log.error("Erreur non gérée", e); // on journalise le détail…
        return ResponseEntity.status(500)
            .body(new ErreurReponse("INTERNAL", "Une erreur est survenue")); // …mais on n'expose rien
    }
}

Un format de réponse cohérent

Définissez un objet d'erreur unique pour toute l'API. Le client sait toujours à quoi s'attendre.

public record ErreurReponse(String code, String message) {}
// { "code": "NOT_FOUND", "message": "Article 42" }

Depuis Spring 6 / Boot 3, on peut aussi s'appuyer sur RFC 7807 (Problem Details) via ProblemDetail, un format d'erreur standardisé que de plus en plus de clients comprennent nativement.

Règles d'or

  • Ne jamais exposer de stack trace ni de message technique brut au client : on journalise en interne, on renvoie un message maîtrisé.
  • Le bon code HTTP : 400 (requête invalide), 401/403 (auth), 404 (introuvable), 409 (conflit), 422 (validation métier), 500 (bug serveur).
  • Un filet de sécurité sur Exception.class pour ne jamais renvoyer une erreur non maîtrisée.
  • Internationaliser les messages via un MessageSource si votre public est multilingue.

En résumé

Avec @RestControllerAdvice, la gestion des erreurs devient un aspect transversal propre : le code métier lève des exceptions parlantes, un seul composant les traduit en réponses HTTP cohérentes et sûres. Moins de try/catch, plus de lisibilité, et une API prévisible pour ses consommateurs.

Sources et références