Salta al contenuto principale

Gestione centralizzata delle eccezioni in Spring Boot

Profile picture for user luca77king

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.