Eccezioni e Gestione degli Errori in Java: Guida Completa
Questa guida ti accompagna passo dopo passo nel mondo delle eccezioni in Java: cosa sono, perché esistono, come usarle correttamente e come creare le tue eccezioni personalizzate. È pensata per chi ha già le basi del linguaggio e vuole scrivere codice più robusto e professionale.

1. Cosa sono le eccezioni in Java?
Durante l’esecuzione di un programma possono verificarsi situazioni anomale: file mancanti, connessioni di rete interrotte, input non valido, divisione per zero e così via. In Java queste situazioni sono gestite tramite le eccezioni (exceptions).
Un’eccezione è un oggetto che rappresenta un errore o un evento imprevisto. Quando si verifica un errore, la JVM “lancia” (throw) un oggetto eccezione e il flusso normale del programma viene interrotto finché l’eccezione non viene catturata (catch) o il programma termina.
Tutte le eccezioni derivano dalla classe base java.lang.Throwable, che ha due sottoclassi principali:
Error: problemi gravi legati alla JVM o all’ambiente (es.OutOfMemoryError). Di solito non vanno gestiti nel codice applicativo.Exception: errori che possono (e dovrebbero) essere gestiti dal programma.
Gerarchia semplificata
In modo molto semplificato:
Throwable
├── Error
└── Exception
├── IOException (checked)
├── SQLException (checked)
├── RuntimeException (unchecked)
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ └── ...
└── ...
2. Eccezioni Java Checked vs Unchecked: Differenze e Utilizzo
In Java esistono due grandi categorie di eccezioni applicative: checked e unchecked. La differenza è importante perché influenza il modo in cui scrivi il codice.
2.1 Eccezioni checked
Le eccezioni checked sono tutte quelle che estendono Exception ma non RuntimeException. Vengono verificate dal compilatore: se un metodo può lanciare una checked exception, hai due possibilità obbligatorie:
- catturarla con un blocco
try-catch; - propagarla dichiarandola nella firma del metodo con
throws.
Esempi tipici di checked exception: IOException, SQLException, ParseException.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class LettoreFile {
public static void leggiFile(String percorso) throws IOException {
// IOException è una checked exception
BufferedReader reader = new BufferedReader(new FileReader(percorso));
String linea = reader.readLine();
System.out.println(linea);
reader.close();
}
}
2.2 Eccezioni unchecked
Le eccezioni unchecked sono tutte quelle che estendono RuntimeException. Il compilatore non ti obbliga a gestirle esplicitamente: puoi scegliere se catturarle o lasciarle propagare.
Esempi comuni: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ArithmeticException.
public class Divisione {
public static int dividi(int a, int b) {
// Può lanciare ArithmeticException (unchecked) se b == 0
return a / b;
}
public static void main(String[] args) {
int risultato = dividi(10, 0); // ArithmeticException a runtime
System.out.println(risultato);
}
}
2.3 Quando usare le Eccezioni Java Checked e Unchecked?
- Checked: per errori che il chiamante può ragionevolmente gestire (es. un file non trovato, una connessione al database fallita).
- Unchecked: per errori di programmazione o di uso scorretto dell’API (es. argomenti non validi, stato incoerente dell’oggetto).
In pratica: le checked indicano situazioni prevedibili e “normali” ma indesiderate; le unchecked indicano bug o utilizzo errato del codice.
3. Gestire le Eccezioni Java con try-catch-finally
Lo strumento principale per gestire le eccezioni è il blocco try-catch-finally. La struttura generale è:
try {
// codice che può lanciare eccezioni
} catch (TipoEccezione1 e) {
// gestione dell'eccezione 1
} catch (TipoEccezione2 e) {
// gestione dell'eccezione 2
} finally {
// codice eseguito SEMPRE, con o senza eccezione
}
3.1 Esempio di base con try-catch
public class GestioneEccezioniBase {
public static void main(String[] args) {
try {
int risultato = 10 / 0; // genera ArithmeticException
System.out.println("Risultato: " + risultato);
} catch (ArithmeticException e) {
System.out.println("Errore: divisione per zero!");
// e.printStackTrace(); // utile in fase di debug
}
System.out.println("Programma terminato.");
}
}
3.2 Il blocco finally
Per approfondire ulteriormente, consulta la Documentazione ufficiale Oracle sulle eccezioni Java.
Il blocco finally viene eseguito sempre, sia che venga lanciata un’eccezione sia che non venga lanciata. È utile per rilasciare risorse (chiudere file, connessioni, stream…).
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class EsempioFinally {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("dati.txt"));
String linea = reader.readLine();
System.out.println(linea);
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
} finally {
// Chiusura della risorsa, se aperta
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
System.out.println("Errore durante la chiusura del file.");
}
}
}
}
}
3.3 Try-with-resources: Gestione Automatica delle Eccezioni Java
Dalla versione Java 7 esiste la sintassi try-with-resources, che chiude automaticamente le risorse che implementano l’interfaccia AutoCloseable. È preferibile quando possibile.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class EsempioTryWithResources {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("dati.txt"))) {
String linea = reader.readLine();
System.out.println(linea);
} catch (IOException e) {
System.out.println("Errore durante la lettura del file: " + e.getMessage());
}
// Nessun finally necessario per chiudere reader
}
}
4. Lanciare Eccezioni Java: throw e throws
4.1 throw: lanciare un’eccezione
La parola chiave throw serve per lanciare esplicitamente un’eccezione da una certa posizione del codice.
public class ValidaEta {
public static void impostaEta(int eta) {
if (eta < 0) {
throw new IllegalArgumentException("L'età non può essere negativa");
}
System.out.println("Età impostata a " + eta);
}
public static void main(String[] args) {
impostaEta(-5); // genera IllegalArgumentException
}
}
4.2 throws: dichiarare le eccezioni di un metodo
La parola chiave throws viene usata nella firma di un metodo per dichiarare che il metodo può lanciare una o più eccezioni checked. In questo modo “sposti” la responsabilità di gestirle al chiamante.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ParserDate {
public static Date parseData(String testoData) throws ParseException {
SimpleDateFormat formato = new SimpleDateFormat("dd/MM/yyyy");
return formato.parse(testoData); // può lanciare ParseException (checked)
}
public static void main(String[] args) {
try {
Date data = parseData("31/02/2023"); // data non valida
System.out.println(data);
} catch (ParseException e) {
System.out.println("Formato di data non valido: " + e.getMessage());
}
}
}
Nota: si possono dichiarare più eccezioni separate da virgola, ad esempio throws IOException, ParseException.
5. Eccezioni Java Personalizzate: Checked e Unchecked
A volte le eccezioni standard di Java non sono sufficienti a descrivere in modo chiaro il problema. In questi casi è utile definire eccezioni personalizzate, specifiche per la tua applicazione o dominio.
5.1 Eccezioni personalizzate checked
Per creare una checked exception personalizzata, estendi Exception (ma non RuntimeException).
// Eccezione checked personalizzata
public class SaldoInsufficienteException extends Exception {
public SaldoInsufficienteException(String messaggio) {
super(messaggio);
}
}
public class ContoBancario {
private double saldo;
public ContoBancario(double saldoIniziale) {
this.saldo = saldoIniziale;
}
public void preleva(double importo) throws SaldoInsufficienteException {
if (importo > saldo) {
throw new SaldoInsufficienteException(
"Saldo insufficiente. Saldo attuale: " + saldo
);
}
saldo -= importo;
}
public double getSaldo() {
return saldo;
}
public static void main(String[] args) {
ContoBancario conto = new ContoBancario(100.0);
try {
conto.preleva(150.0);
} catch (SaldoInsufficienteException e) {
System.out.println("Operazione fallita: " + e.getMessage());
}
System.out.println("Saldo finale: " + conto.getSaldo());
}
}
5.2 Eccezioni personalizzate unchecked
Per creare una eccezione unchecked personalizzata, estendi RuntimeException. È utile per segnalare errori di programmazione specifici del tuo dominio.
// Eccezione unchecked personalizzata
public class ConfigurazioneNonValidaException extends RuntimeException {
public ConfigurazioneNonValidaException(String messaggio) {
super(messaggio);
}
}
public class ServizioApplicazione {
private String urlApi;
public ServizioApplicazione(String urlApi) {
if (urlApi == null || urlApi.isBlank()) {
throw new ConfigurazioneNonValidaException(
"L'URL dell'API non può essere nullo o vuoto"
);
}
this.urlApi = urlApi;
}
public void esegui() {
System.out.println("Chiamata all'API: " + urlApi);
}
public static void main(String[] args) {
// Genererà l'eccezione unchecked
ServizioApplicazione servizio = new ServizioApplicazione("");
servizio.esegui();
}
}
6. Esempi Pratici di Gestione delle Eccezioni Java
6.1 Validazione dell’input utente
In questo esempio leggiamo un numero da riga di comando e gestiamo diversi possibili errori: nessun argomento passato, formato non numerico, numero negativo.
public class LeggiNumero {
public static int leggiNumeroPositivo(String[] args) {
if (args.length == 0) {
throw new IllegalArgumentException("Devi passare un numero come argomento.");
}
try {
int valore = Integer.parseInt(args[0]);
if (valore <= 0) {
throw new IllegalArgumentException("Il numero deve essere positivo.");
}
return valore;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("L'argomento deve essere un numero intero.", e);
}
}
public static void main(String[] args) {
try {
int numero = leggiNumeroPositivo(args);
System.out.println("Hai inserito: " + numero);
} catch (IllegalArgumentException e) {
System.out.println("Errore di input: " + e.getMessage());
}
}
}
6.2 Wrapper di eccezioni (exception chaining)
6.2 Wrapping di Eccezioni Java (exception chaining)
// Eccezione di dominio
public class ErroreRepositoryException extends Exception {
public ErroreRepositoryException(String messaggio, Throwable causa) {
super(messaggio, causa);
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UtenteRepository {
public String trovaNomeUtente(int id) throws ErroreRepositoryException {
String url = "jdbc:esempio://localhost:5432/db";
try (Connection conn = DriverManager.getConnection(url);
PreparedStatement stmt = conn.prepareStatement("SELECT nome FROM utenti WHERE id = ?")) {
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return rs.getString("nome");
} else {
return null;
}
} catch (SQLException e) {
// Wrapping dell'eccezione tecnica in una eccezione di dominio
throw new ErroreRepositoryException("Impossibile leggere l'utente dal database", e);
}
}
}
7. Buone Pratiche per le Eccezioni Java
- Non ignorare le eccezioni: evitare blocchi
catch (Exception e) {}vuoti. Almeno logga l’errore o ripristina una situazione consistente. - Non usare le eccezioni per il flusso normale: non dovrebbero sostituire gli
if, ma gestire situazioni eccezionali. - Dai messaggi chiari: i messaggi delle eccezioni dovrebbero aiutare a capire cosa è andato storto e, se possibile, come rimediare.
- Preferisci eccezioni specifiche: usare eccezioni mirate (es.
FileNotFoundExceptioninvece diException) rende il codice più chiaro. - Mantieni la causa originale: quando fai wrapping di un’eccezione, passa sempre la causa al costruttore con
super(messaggio, causa). - Gestisci vicino alla fonte o ai bordi del sistema: in genere o gestisci subito l’errore dove avviene, o lo lasci propagare fino a un livello alto che può reagire in modo sensato (es. mostrare un messaggio all’utente).
8. Riepilogo
In questa guida hai visto cosa sono le eccezioni in Java, la differenza tra checked e unchecked, come usare i blocchi try-catch-finally, le parole chiave throw e throws, come creare eccezioni personalizzate e alcuni esempi pratici di gestione degli errori.
Imparare a progettare e gestire bene le eccezioni è fondamentale per scrivere applicazioni affidabili, manutenibili e facili da debuggare. Quando scrivi nuovo codice, chiediti sempre: “Cosa può andare storto qui?” e decidi consapevolmente se e come gestire le eccezioni che possono verificarsi.
Lascia un commento