Aller au contenu

Gestion globale des erreurs (cours complet)

Pourquoi centraliser les erreurs ?

Si chaque controller gère ses erreurs :

Objectif : un seul format, partout.


Exemple de format d’erreur stable

{
  "timestamp": "2025-12-31T09:00:00",
  "status": 400,
  "error": "BAD_REQUEST",
  "message": "Tatouage déjà utilisé : T123",
  "path": "/api/chiens"
}

Pourquoi c’est important ?


ErrorResponse

public record ErrorResponse(
  LocalDateTime timestamp,
  int status,
  String error,
  String message,
  String path,
  Map<String, String> details
) {}

details sert pour la validation (champ et erreur).


ControllerAdvice : la tour de contrôle

@RestControllerAdvice
public class GlobalExceptionHandler {

  private ErrorResponse base(HttpStatus status, String message, HttpServletRequest req) {
    return new ErrorResponse(LocalDateTime.now(), status.value(), status.name(), message, req.getRequestURI(), Map.of());
  }

  @ExceptionHandler(IllegalArgumentException.class)
  public ResponseEntity<ErrorResponse> illegalArg(IllegalArgumentException ex, HttpServletRequest req) {
    return ResponseEntity.badRequest().body(base(HttpStatus.BAD_REQUEST, ex.getMessage(), req));
  }

  @ExceptionHandler(IllegalStateException.class)
  public ResponseEntity<ErrorResponse> illegalState(IllegalStateException ex, HttpServletRequest req) {
    return ResponseEntity.status(HttpStatus.CONFLICT).body(base(HttpStatus.CONFLICT, ex.getMessage(), req));
  }
}

Gestion des erreurs de validation

Quand @Valid échoue, Spring lance MethodArgumentNotValidException.

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> validation(MethodArgumentNotValidException ex, HttpServletRequest req) {
  Map<String, String> details = new HashMap<>();
  ex.getBindingResult().getFieldErrors().forEach(err -> details.put(err.getField(), err.getDefaultMessage()));

  ErrorResponse res = new ErrorResponse(LocalDateTime.now(), 400, "BAD_REQUEST",
    "Validation échouée", req.getRequestURI(), details);

  return ResponseEntity.badRequest().body(res);
}

Exemple : réponse validation

{
  "message": "Validation échouée",
  "details": {
    "email": "doit être une adresse email valide",
    "nom": "ne doit pas être vide"
  }
}

Tester les erreurs (important !)

@Test
void invalid_payload_returns_details() throws Exception {
  mockMvc.perform(post("/api/proprietaires")
      .contentType(MediaType.APPLICATION_JSON)
      .content("{"nom":"","prenom":"","email":"pasunemail"}"))
    .andExpect(status().isBadRequest())
    .andExpect(jsonPath("$.details.email").exists());
}

A faire

  1. Créez une exception NotFoundException pour le 404.
  2. Faites un handler dédié pour 404.