Exception Handling
try-catch, throws, and custom exceptions
1. Exception Overview
Exceptions are runtime events that disrupt normal program flow. Proper exception handling ensures robust, maintainable applications.
Checked Exceptions
Must be handled at compile time
IOException, SQLException
Unchecked Exceptions
Runtime exceptions, optional handling
NullPointerException, ArrayIndexOutOfBoundsException
Errors
Serious problems, typically not recoverable
OutOfMemoryError, StackOverflowError
Exception Flow
1
Exception Occurs
Runtime error in code execution
2
Exception Object Created
JVM creates exception instance
3
Handler Search
JVM looks for appropriate catch block
4
Handle or Terminate
Execute handler or terminate program
2. Exception Hierarchy
Java’s exception hierarchy is built on the Throwable class, branching into Errors and Exceptions.
Exception Examples
// Checked Exception - Must handle
try {
FileReader file = new FileReader("nonexistent.txt");
} catch (IOException e) {
System.out.println("File not found: " + e.getMessage());
}
// Unchecked Exception - Optional handling
String str = null;
try {
int length = str.length(); // NullPointerException
} catch (NullPointerException e) {
System.out.println("Null reference error");
}
3. Try-Catch Blocks
try {}
Code that might throw exception
catch (ExceptionType e) {}
Handle specific exception
finally {}
Always executes (optional)
Basic Try-Catch
Basic Try-Catch
try {
int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!");
}
Multiple Catch Blocks
Multiple Catch Blocks
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
int result = 10 / 0; // ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array index out of bounds");
} catch (ArithmeticException e) {
System.out.println("Arithmetic error");
} catch (Exception e) {
System.out.println("General exception: " + e.getMessage());
}
Multi-Catch (Java 7+)
Multi-Catch
try {
// Some risky operations
performOperations();
} catch (IOException | SQLException e) {
System.out.println("IO or Database error: " + e.getMessage());
logger.error("Operation failed", e);
}
Try-Catch-Finally
Try-Catch-Finally
FileReader file = null;
try {
file = new FileReader("data.txt");
// Process file
} catch (IOException e) {
System.out.println("File error: " + e.getMessage());
} finally {
// Always executes
if (file != null) {
try {
file.close();
} catch (IOException e) {
System.out.println("Error closing file");
}
}
}
Try-With-Resources (Java 7+)
Try-With-Resources
// Automatically closes resources
try (FileReader file = new FileReader("data.txt");
BufferedReader buffer = new BufferedReader(file)) {
String line = buffer.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("File processing error: " + e.getMessage());
}
// Resources automatically closed here
Execution Order
1
Try Block Executes
If no exception → Skip catch blocks
2
Exception Occurs
Jump to appropriate catch block
3
Finally Block
Always executes (if present)
4. Throws & Throw
throws
- Used in: Method signature
- Purpose: Declares potential exceptions
- Action: Passes responsibility to caller
throw
- Used in: Method body
- Purpose: Actually throws an exception
- Action: Creates and throws exception object
Using ‘throws’ Keyword
Using throws
// Method declares it might throw IOException
public void readFile(String filename) throws IOException {
FileReader file = new FileReader(filename);
// File operations...
}
// Method with multiple exceptions
public void databaseOperation() throws SQLException, ClassNotFoundException {
// Database operations...
}
// Caller must handle the exception
public void processData() {
try {
readFile("data.txt");
} catch (IOException e) {
System.out.println("Failed to read file: " + e.getMessage());
}
}
Using ‘throw’ Keyword
Using throw
// Explicitly throwing an exception
public void validateAge(int age) {
if (age 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
if (age > 150) {
throw new IllegalArgumentException("Age seems unrealistic");
}
}
// Re-throwing caught exception
public void processFile(String filename) throws IOException {
try {
// File operations
Files.readAllLines(Paths.get(filename));
} catch (IOException e) {
// Log the error
logger.error("File processing failed", e);
throw e; // Re-throw to caller
}
}
5. Custom Exceptions
Custom exceptions provide specific error handling for your application domain, making code more readable and maintainable.
Custom Checked Exception
Custom Checked Exception
// Extends Exception for checked exception
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
super("Insufficient funds: $" + amount + " required");
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
Custom Unchecked Exception
Custom Unchecked Exception
// Extends RuntimeException for unchecked exception
public class InvalidUserException extends RuntimeException {
private String userId;
public InvalidUserException(String userId, String message) {
super(message);
this.userId = userId;
}
public String getUserId() {
return userId;
}
}
Exception Design Guidelines
- Descriptive Names: Use clear, specific names that describe the error condition
- Meaningful Messages: Provide context about what went wrong and how to fix it
- Additional Data: Include relevant information for error handling (error codes, timestamps)
- Proper Hierarchy: Extend appropriate exception class
Complete Custom Exception Example
Banking System Example
// Custom exception for banking operations
public class BankingException extends Exception {
private String accountNumber;
private String errorCode;
public BankingException(String accountNumber, String errorCode, String message) {
super(message);
this.accountNumber = accountNumber;
this.errorCode = errorCode;
}
// Getters
public String getAccountNumber() { return accountNumber; }
public String getErrorCode() { return errorCode; }
}
// Usage in banking service
public class BankAccount {
private double balance;
private String accountNumber;
public void withdraw(double amount) throws BankingException {
if (amount 0) {
throw new BankingException(accountNumber, "INVALID_AMOUNT",
"Withdrawal amount must be positive");
}
if (amount > balance) {
throw new BankingException(accountNumber, "INSUFFICIENT_FUNDS",
"Insufficient balance. Available: $" + balance +
", Requested: $" + amount);
}
balance -= amount;
}
}
// Exception handling
try {
account.withdraw(1000);
} catch (BankingException e) {
System.out.println("Banking Error [" + e.getErrorCode() + "]: " + e.getMessage());
System.out.println("Account: " + e.getAccountNumber());
}
Custom Exception Hierarchy
Exception Hierarchy
// Base application exception
public class ApplicationException extends Exception {
protected String errorCode;
protected long timestamp;
public ApplicationException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
this.timestamp = System.currentTimeMillis();
}
}
// Specific business exceptions
public class UserNotFoundException extends ApplicationException {
public UserNotFoundException(String userId) {
super("USER_NOT_FOUND", "User not found: " + userId);
}
}
public class ValidationException extends ApplicationException {
public ValidationException(String field, String message) {
super("VALIDATION_ERROR", "Invalid " + field + ": " + message);
}
}
6. Best Practices
Good vs Bad Examples
Bad: Generic Exception Catching
try {
// Some operations
performDatabaseOperation();
processFile();
} catch (Exception e) {
// Too generic!
System.out.println("Something went wrong");
}
Good: Specific Exception Handling
try {
performDatabaseOperation();
processFile();
} catch (SQLException e) {
logger.error("Database operation failed", e);
// Handle database-specific recovery
} catch (IOException e) {
logger.error("File processing failed", e);
// Handle file-specific recovery
}
Bad: Empty Catch Block
try {
int value = Integer.parseInt(userInput);
} catch (NumberFormatException e) {
// Silent failure - very bad!
}
Good: Proper Error Handling
try {
int value = Integer.parseInt(userInput);
} catch (NumberFormatException e) {
logger.warn("Invalid number format: " + userInput, e);
showUserMessage("Please enter a valid number");
return getDefaultValue();
}
Exception Logging Levels
- ERROR: System errors, exceptions that affect functionality
- WARN: Recoverable issues, degraded functionality
- INFO: Expected exceptions, business logic flow
- DEBUG: Detailed troubleshooting information
Testing Exception Handling
JUnit Exception Testing
// JUnit 5 - Test expected exception
@Test
void testInvalidAge() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> validateAge(-5)
);
assertEquals("Age cannot be negative", exception.getMessage());
}
// Test custom exception
@Test
void testInsufficientFunds() {
BankAccount account = new BankAccount(100);
BankingException exception = assertThrows(
BankingException.class,
() -> account.withdraw(200)
);
assertEquals("INSUFFICIENT_FUNDS", exception.getErrorCode());
}
Documenting Exceptions
JavaDoc for Exceptions
/**
* Transfers money between accounts
*
* @param fromAccount source account
* @param toAccount destination account
* @param amount transfer amount
* @throws BankingException if insufficient funds or account issues
* @throws IllegalArgumentException if amount is negative or zero
* @throws AccountNotFoundException if either account doesn't exist
*/
public void transfer(String fromAccount, String toAccount, double amount)
throws BankingException, IllegalArgumentException, AccountNotFoundException {
// Implementation
}