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.
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.classpour ne jamais renvoyer une erreur non maîtrisée. - Internationaliser les messages via un
MessageSourcesi 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.