Java Exception Handling: try-catch-finally, Custom Exceptions
Exceptions represent exceptional conditions that disrupt the normal flow of a program. Java's exception mechanism separates error-handling code from regular code via try-catch-finally blocks, and distinguishes between checked exceptions (must be declared or caught) and unchecked exceptions (runtime errors). Mastering exception handling leads to more robust, maintainable applications.
Contents
Exception Hierarchy
- Throwable: root of all error/exception classes
- Error (serious JVM problems (OutOfMemoryError, StackOverflowError)) do not catch
- Exception: base for all application exceptions
- RuntimeException (extends Exception): unchecked; not required to be declared or caught (NullPointerException, IndexOutOfBoundsException, IllegalArgumentException)
- Checked exceptions (all Exception subclasses that are NOT RuntimeException (IOException, SQLException, ParseException)) must be declared with
or caughtthrows
// Unchecked — no need to declare throws
int[] arr = new int[3];
arr[5] = 1; // throws ArrayIndexOutOfBoundsException at runtime
String s = null;
s.length(); // throws NullPointerException at runtime
// Checked — compiler enforces handling
Files.readString(Path.of("missing.txt")); // IOException — must catch or declare throws
try-catch-finally
A trycatchfinallytry-with-resources
// Basic try-catch
try {
int result = 10 / 0; // throws ArithmeticException
System.out.println(result);
} catch (ArithmeticException e) {
System.err.println("Division by zero: " + e.getMessage());
}
// Multiple catch blocks — most specific first
try {
String s = null;
System.out.println(s.length()); // NullPointerException
int[] arr = new int[3];
arr[5] = 1; // ArrayIndexOutOfBoundsException
} catch (NullPointerException e) {
System.err.println("Null reference: " + e);
} catch (ArrayIndexOutOfBoundsException e) {
System.err.println("Array bounds: " + e);
} catch (RuntimeException e) {
System.err.println("Some runtime error: " + e); // catches any other RuntimeException
}
// finally — always executes (even if exception thrown or return used)
Connection conn = null;
try {
conn = DriverManager.getConnection(url);
// use connection
} catch (SQLException e) {
System.err.println("DB error: " + e);
} finally {
if (conn != null) {
try { conn.close(); } catch (SQLException ignored) {}
}
// Prefer try-with-resources (see below) over manual finally cleanup
}
// Rethrowing
try {
riskyOperation();
} catch (IOException e) {
log.error("Failed", e);
throw e; // rethrow same exception
}
// Throwing a new exception
try {
int age = Integer.parseInt("abc");
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid age format", e); // chained
}
ExceptionThrowableMulti-catch and Exception Chaining
Multi-catch (Java 7+) collapses multiple catch|
// Multi-catch (Java 7+) — handle multiple types the same way
try {
if (flag) throw new IOException("io");
else throw new ParseException("parse", 0);
} catch (IOException | ParseException e) {
// e is effectively final — cannot reassign
System.err.println("Input error: " + e.getMessage());
}
// Exception chaining — preserve the original cause
public User loadUser(String path) throws ServiceException {
try {
String json = Files.readString(Path.of(path));
return parse(json);
} catch (IOException e) {
// Wrap in domain exception, preserving original as cause
throw new ServiceException("Failed to load user from " + path, e);
}
}
// Access the chain
try {
loadUser("users.json");
} catch (ServiceException e) {
System.err.println(e.getMessage());
System.err.println("Caused by: " + e.getCause()); // the original IOException
e.printStackTrace(); // shows full chain
}
// getSuppressed() — exceptions added to another via addSuppressed()
// Used automatically by try-with-resources
try-with-resources
try-with-resourcesAutoCloseablefinallyclose()
// Single resource — automatically closed after try block
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Error: " + e);
}
// reader.close() called automatically — even if exception thrown
// Multiple resources — closed in reverse order of declaration
try (var conn = DriverManager.getConnection(url);
var stmt = conn.createStatement();
var rs = stmt.executeQuery("SELECT * FROM users")) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
System.err.println("DB error: " + e);
}
// Suppressed exceptions — if both body and close() throw,
// the close() exception is suppressed and attached to the body exception
try (var r = new FaultyResource()) {
throw new RuntimeException("body exception");
// r.close() also throws — that exception is suppressed
} catch (RuntimeException e) {
System.err.println(e.getMessage()); // "body exception"
for (Throwable s : e.getSuppressed()) {
System.err.println("Suppressed: " + s.getMessage()); // "close exception"
}
}
// Effectively-final variables in try-with-resources (Java 9+)
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
try (br) { // no re-declaration needed — br must be effectively final
System.out.println(br.readLine());
}
Custom Exceptions
Custom exceptions convey domain-specific failure conditions. Extend ExceptionRuntimeException
// Checked custom exception — extends Exception
public class InsufficientFundsException extends Exception {
private final double amount;
public InsufficientFundsException(double amount) {
super("Insufficient funds: need " + amount + " more");
this.amount = amount;
}
public InsufficientFundsException(double amount, Throwable cause) {
super("Insufficient funds: need " + amount + " more", cause);
this.amount = amount;
}
public double getAmount() { return amount; }
}
// Unchecked custom exception — extends RuntimeException
public class UserNotFoundException extends RuntimeException {
private final long userId;
public UserNotFoundException(long userId) {
super("User not found: " + userId);
this.userId = userId;
}
public long getUserId() { return userId; }
}
// Using custom exceptions
class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(amount - balance);
}
balance -= amount;
}
}
// Catching custom exceptions
try {
account.withdraw(500.0);
} catch (InsufficientFundsException e) {
System.err.printf("Need %.2f more%n", e.getAmount());
}
RuntimeException