Aller au contenu

Quelques précisions sur la gestion des messages

Faut-il utiliser ou pas un ControllerAdvice ? Si oui, dans quel cas ?

alt text

ça dépend du type d’exception.


Ce que Spring Boot fait TOUT SEUL (sans un @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.

@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.


Ce que Spring Boot NE fait PAS sans @ControllerAdvice

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()));
    }
}

Le tableau de décision complet

Situation Sans @ControllerAdvice Avec @ControllerAdvice Verdict
@Valid échoue sur un DTO 400 auto avec détails 400 personnalisé Inutile (sauf pour reformater)
@PathVariable type invalide (ex: /books/abc) 400 auto 400 personnalisé Inutile
Votre BookNotFoundException 500 générique 404 propre Indispensable
Votre InsufficientStockException 500 générique 409 propre Indispensable
NullPointerException inattendue 500 générique 500 avec message propre Utile
Uniformiser le format JSON des erreurs Format Spring par défaut Format uniforme custom Très utile

Le cas hybride : reformater les erreurs de validation

@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));
    }
}

Résumé

@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.