
Gestion globale des exceptions avec `@ControllerAdvice` et `@ExceptionHandler`
Apprenez à implémenter une gestion globale et cohérente des exceptions dans vos applications Spring Boot en utilisant @ControllerAdvice et @ExceptionHandler pour des API plus robustes.
Le problème des exceptions non gérées et du code répétitif
Dans toute application, des erreurs peuvent survenir : une ressource demandée n'existe pas, des données d'entrée sont invalides, une dépendance externe échoue, etc. Sans mécanisme de gestion approprié, ces erreurs (exceptions) peuvent remonter jusqu'au conteneur de servlets et entraîner des réponses génériques et peu informatives pour le client (comme une page d'erreur HTML par défaut de Spring Boot ou une trace de pile brute), ou pire, planter la requête.
Une approche naïve consisterait à entourer chaque appel potentiellement problématique dans vos méthodes de contrôleur avec des blocs try-catch. Cependant, cela conduit rapidement à un code de contrôleur volumineux, répétitif, difficile à lire et à maintenir. La logique de gestion des erreurs se retrouve dispersée dans toute l'application, et il devient compliqué d'assurer une réponse d'erreur cohérente pour les clients de votre API.
Spring MVC (et donc Spring Boot) fournit une solution beaucoup plus élégante et centralisée pour gérer les exceptions qui se produisent pendant le traitement des requêtes : les annotations @ControllerAdvice et @ExceptionHandler.
@ControllerAdvice : Un assistant global pour vos contrôleurs
L'annotation @ControllerAdvice est une spécialisation de l'annotation @Component. Elle permet de créer une classe qui contient des méthodes applicables globalement à plusieurs (ou tous) les contrôleurs de votre application. C'est une sorte de "conseiller" ou d'"assistant" pour vos contrôleurs.
Une classe annotée avec @ControllerAdvice peut contenir des méthodes annotées avec :
@ExceptionHandler: Pour gérer spécifiquement certains types d'exceptions levées par les méthodes des contrôleurs. C'est le cas d'usage le plus fréquent pour la gestion globale des erreurs.@InitBinder: Pour personnaliser la liaison des données de requête (data binding).@ModelAttribute: Pour ajouter des attributs communs au modèle de toutes les vues rendues par les contrôleurs concernés.
En plaçant la logique de gestion des exceptions dans une classe @ControllerAdvice, vous la sortez de vos contrôleurs individuels et la centralisez en un seul endroit, améliorant ainsi la clarté et la maintenabilité du code.
@ExceptionHandler : Intercepter et traiter des exceptions spécifiques
L'annotation @ExceptionHandler est utilisée sur une méthode (typiquement au sein d'une classe @ControllerAdvice) pour indiquer que cette méthode doit être invoquée lorsqu'une exception d'un type spécifique (ou d'un de ses sous-types) est levée par une méthode de contrôleur (ou un filtre/intercepteur associé) et n'est pas déjà gérée localement par un autre @ExceptionHandler dans le contrôleur lui-même.
La méthode annotée avec @ExceptionHandler peut prendre plusieurs types d'arguments, notamment :
- L'exception elle-même (par exemple,
ResourceNotFoundException ex). - La requête HTTP (
HttpServletRequestouWebRequest). - La réponse HTTP (
HttpServletResponse).
Et surtout, elle peut retourner différents types pour construire la réponse d'erreur :
ResponseEntity>: Le choix le plus flexible, permettant de contrôler totalement le code de statut, les en-têtes et le corps de la réponse d'erreur (souvent un DTO d'erreur).- Un objet (ex: un DTO d'erreur) : Spring utilisera alors
@ResponseBody(implicite dans@ControllerAdvicesi elle est aussi annotée@RestControllerAdvice) pour sérialiser l'objet et utilisera un code de statut par défaut (souvent 500) ou celui défini par@ResponseStatussur la méthode handler. ModelAndView: Pour rediriger vers une page d'erreur HTML dans une application MVC traditionnelle.
Mise en oeuvre d'un gestionnaire d'exceptions global
Créons un exemple de classe RestExceptionHandler pour gérer différentes erreurs courantes dans une API REST :
package com.certiquizz.monappliboot.exception;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// @ControllerAdvice ou @RestControllerAdvice (cette dernière ajoute @ResponseBody implicitement)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler { // Etendre cette classe aide à gérer les exceptions Spring MVC internes
// Gestionnaire pour une exception personnalisée "ResourceNotFoundException"
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntityDans cet exemple :
- La classe est annotée avec
@ControllerAdvice. - Elle étend
ResponseEntityExceptionHandlerpour bénéficier de la gestion par défaut de certaines exceptions Spring MVC internes (commeMethodArgumentNotValidException) tout en nous permettant de personnaliser la réponse. - Une méthode
handleResourceNotFoundest définie pour gérer notre exception métier personnaliséeResourceNotFoundException. Elle retourne uneResponseEntityavec un statut 404 et un corps JSON structuré contenant des détails sur l'erreur. - La méthode
handleMethodArgumentNotValidest surchargée pour personnaliser la réponse en cas d'échec de validation (via@Valid). Elle extrait les messages d'erreur spécifiques à chaque champ et les inclut dans la réponse 400. - Une méthode
handleGenericExceptionsert de filet de sécurité pour toutes les autres exceptions non prévues. Elle logue l'erreur (essentiel pour le débogage) et retourne une réponse 500 générique au client, sans fuiter de détails internes.
Structurer la réponse d'erreur
Il est fortement recommandé de définir une structure cohérente pour vos réponses d'erreur JSON. Au lieu d'utiliser une Map générique comme dans l'exemple ci-dessus, créez un DTO spécifique pour les erreurs.
package com.certiquizz.monappliboot.dto.error;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@JsonInclude(JsonInclude.Include.NON_NULL) // N'inclut pas les champs null dans le JSON
public class ApiErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
private List validationErrors;
private Map details; // Pour des détails supplémentaires optionnels
// Constructeurs, Getters, Setters...
public ApiErrorResponse(HttpStatus status, String message, String path) {
this.timestamp = LocalDateTime.now();
this.status = status.value();
this.error = status.getReasonPhrase();
this.message = message;
this.path = path;
}
// Constructeur pour erreurs de validation
public ApiErrorResponse(HttpStatus status, String message, String path, List validationErrors) {
this(status, message, path);
this.validationErrors = validationErrors;
}
// ... autres constructeurs / méthodes si nécessaire ...
// Getters/Setters
}
Vous utiliseriez ensuite ce DTO dans vos méthodes @ExceptionHandler :
// ... dans RestExceptionHandler ...
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity handleResourceNotFound(ResourceNotFoundException ex, WebRequest request) {
String path = request.getDescription(false).replace("uri=", "");
ApiErrorResponse apiError = new ApiErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage(), path);
return new ResponseEntity<>(apiError, HttpStatus.NOT_FOUND);
}
// ... adapter les autres handlers de la même manière ... Avantages et conclusion
L'utilisation combinée de @ControllerAdvice et @ExceptionHandler offre une approche robuste et centralisée pour la gestion des erreurs dans les applications Spring Boot :
- Centralisation : Toute la logique de gestion des erreurs est regroupée.
- Code de contrôleur propre : Les contrôleurs se concentrent sur leur logique principale sans être pollués par des
try-catchrépétitifs. - Réponses d'erreur cohérentes : Assure que tous les clients reçoivent des réponses d'erreur structurées et uniformes.
- Séparation des préoccupations : La gestion des erreurs est clairement séparée de la logique métier et de la logique de contrôle.
- Flexibilité : Permet de gérer différents types d'exceptions de manière spécifique et de personnaliser entièrement la réponse HTTP.
C'est la méthode standard et recommandée pour gérer les exceptions dans les API REST développées avec Spring Boot, contribuant à la création d'applications plus maintenables, robustes et conviviales pour les développeurs consommateurs.