Gestione centralizzata delle eccezioni in Spring Boot

LT
Luca Terribili
Autore

Quando si sviluppano applicazioni complesse, le eccezioni diventano inevitabili. Non importa quanto sia curato il codice: errori di validazione, dati mancanti o problemi interni prima o poi arrivano. Il punto non è evitarli del tutto, ma gestirli in modo pulito e coerente, evitando di disseminare il codice con blocchi try/catch o risposte HTTP costruite a mano in ogni controller. È qui che entra in gioco la gestione centralizzata delle eccezioni, una delle pratiche più eleganti e potenti offerte da Spring Boot per mantenere ordine, leggibilità e coerenza nelle API.

Perché centralizzare la gestione delle eccezioni

Un’applicazione ben strutturata deve sapere comunicare chiaramente con chi la utilizza, che si tratti di un frontend o di un servizio esterno. Quando qualcosa va storto, la risposta deve essere prevedibile, leggibile e coerente con il resto dell’API. Senza una gestione centralizzata, ogni controller rischia di restituire errori in formati diversi, con messaggi incoerenti o codici di stato HTTP sbagliati. Questo genera confusione, rallenta il debug e complica l’integrazione.

Centralizzare significa creare un punto unico in cui tutte le eccezioni passano prima di arrivare al client. Da qui possiamo decidere cosa mostrare, cosa nascondere e come formattare la risposta.

@ControllerAdvice e @ExceptionHandler: il cuore del sistema

Spring Boot mette a disposizione due strumenti chiave per ottenere una gestione centralizzata elegante: @ControllerAdvice e @ExceptionHandler. Il primo intercetta le eccezioni lanciate dai controller, mentre il secondo indica come trattarle. Insieme formano una sorta di firewall per gli errori, che blocca ogni eccezione e la trasforma in una risposta HTTP strutturata.

Ecco un esempio concreto di implementazione:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ResponseEntity<Map<String, Object>> handleEntityNotFound(EntityNotFoundException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Not Found");
        body.put("message", ex.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleValidationErrors(MethodArgumentNotValidException ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Validation Error");
        body.put("message", ex.getBindingResult().getAllErrors().get(0).getDefaultMessage());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
        Map<String, Object> body = new HashMap<>();
        body.put("error", "Internal Server Error");
        body.put("message", ex.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
    }
}

Questa classe intercetta tutte le eccezioni più comuni e costruisce una risposta JSON pulita. L’annotazione @RestControllerAdvice permette di estendere la logica a tutti i controller senza dover modificare nulla nel codice esistente. È una soluzione trasparente e non intrusiva.

Creare eccezioni personalizzate per maggiore chiarezza

In un’applicazione reale, è utile creare delle eccezioni specifiche per i diversi contesti. Ad esempio, per un’entità non trovata:

public class EntityNotFoundException extends RuntimeException {
    public EntityNotFoundException(String message) {
        super(message);
    }
}

In questo modo, quando qualcosa non viene trovato nel database, si può semplicemente scrivere:

throw new EntityNotFoundException("Utente non trovato");

Spring intercetterà automaticamente l’eccezione e restituirà una risposta coerente e leggibile, senza dover riscrivere nulla nei controller.

I vantaggi reali della gestione centralizzata

  • Codice più pulito: nessun blocco try/catch ripetuto in ogni metodo.

  • Coerenza: tutti gli errori vengono restituiti con lo stesso formato JSON.

  • Manutenzione facilitata: basta modificare una sola classe per cambiare la gestione di tutti gli errori.

  • Maggiore controllo: è possibile decidere quali messaggi mostrare al client e quali nascondere per motivi di sicurezza.

Conclusione

La gestione centralizzata delle eccezioni non è un dettaglio secondario, ma una parte fondamentale della qualità di un’API. Un sistema che restituisce errori chiari, coerenti e ben formattati è un sistema più professionale, più facile da mantenere e più semplice da integrare. In Spring Boot basta una classe con @RestControllerAdvice per ottenere tutto questo. È uno di quegli strumenti che, una volta introdotti nel progetto, fanno chiedere come si sia potuto sviluppare senza di esso. Daje.