Faut-il utiliser ou pas un ControllerAdvice ? Si oui, dans quel cas ?
ControllerAdvice
ça dépend du type d’exception.
@ControllerAdvice
Quand vous utilisez @Valid ou @Validated sur un paramètre de méthode dans un controller, Spring Boot intercepte automatiquement les MethodArgumentNotValidException et retourne un 400 Bad Request avec un corps JSON.
@Valid
@Validated
MethodArgumentNotValidException
@PostMapping("/books") public BookResponse create(@Valid @RequestBody CreateBookRequest request) { return bookService.create(request); }
public record CreateBookRequest( @NotBlank(message = "Le titre est obligatoire") String title, @Pattern(regexp = "\\d{13}", message = "ISBN doit avoir une longueur de 13 chiffres") String isbn ) {}
Si vous envoyez un titre vide, Spring Boot répond automatiquement comme ci-dessous :
{ "timestamp": "2024-01-15T10:30:00", "status": 400, "errors": ["titre: Le titre est obligatoire"], "path": "/api/books" }
Conclusion : @ControllerAdvice est inutile pour les erreurs de validation Bean Validation. Spring Boot les gère seul via DefaultHandlerExceptionResolver et ResponseEntityExceptionHandler.
DefaultHandlerExceptionResolver
ResponseEntityExceptionHandler
Dès que vous lancez vos propres exceptions, Spring ne sait pas comment les traiter et retourne une réponse générique 500 Internal Server Error, même si l’erreur est purement métier.
// exception personnalisée public class BookNotFoundException extends RuntimeException { public BookNotFoundException(Long id) { super("Livre introuvable : " + id); } } // le service public Book findById(Long id) { return repository.findById(id).orElseThrow(() -> new BookNotFoundException(id)); }
Sans @ControllerAdvice, le client reçoit :
{ "timestamp": "2024-01-15T10:30:00", "status": 500, "error": "Internal Server Error", "path": "/api/books/99" }
Alors que ce devrait être un 404. C’est là que @ControllerAdvice devient indispensable :
@RestControllerAdvice public class GlobalExceptionHandler { // Sans ça : 500 générique. Avec ça : 404 propre. @ExceptionHandler(BookNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(BookNotFoundException ex) { return ResponseEntity.status(404) .body(new ErrorResponse("NOT_FOUND", ex.getMessage())); } // Sans ça : 500 générique. Avec ça : 409 avec contexte métier. @ExceptionHandler(InsufficientStockException.class) public ResponseEntity<ErrorResponse> handleStock(InsufficientStockException ex) { return ResponseEntity.status(409) .body(new ErrorResponse("INSUFFICIENT_STOCK", ex.getMessage())); } }
@PathVariable
/books/abc
BookNotFoundException
InsufficientStockException
NullPointerException
@ControllerAdvice devient utile pour la validation si vous voulez un format JSON différent de celui que Spring génère par défaut :
@RestControllerAdvice public class GlobalExceptionHandler { // Optionnel — seulement si vous voulez un format d'erreur différent @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<?> handleValidation(MethodArgumentNotValidException ex) { // Spring génère un format verbeux par défaut. // Ici on choisit un format épuré et cohérent avec le reste de l'API. List<String> errors = ex.getBindingResult().getFieldErrors().stream() .map(fe -> fe.getField() + ": " + fe.getDefaultMessage()) .toList(); return ResponseEntity.badRequest() .body(Map.of("code", "VALIDATION_FAILED", "errors", errors)); } }
@ControllerAdvice est inutile pour les erreurs @Valid (Spring s’en charge), mais indispensable dès que vous avez des exceptions métier personnalisées — sans lui, elles retournent toutes un 500 générique, peu importe leur sens fonctionnel.