Exception Handling
Build resilient applications through proper error management
Exception Handling in Java
Master Java exception handling with free flashcards and spaced repetition practice. This lesson covers try-catch blocks, custom exceptions, exception hierarchies, and best practicesβessential concepts for writing robust, production-ready Java applications.
Welcome to Exception Handling π»
Exception handling is one of Java's most powerful features for building reliable software. When things go wrongβa file doesn't exist, a network connection fails, or invalid data arrivesβyour program needs a systematic way to detect, report, and recover from these exceptional conditions. Without proper exception handling, programs crash unexpectedly, leaving users frustrated and data corrupted.
In this lesson, you'll learn how Java's exception mechanism works from the ground up. You'll understand the difference between checked and unchecked exceptions, master the try-catch-finally pattern, create your own custom exceptions, and apply industry best practices that separate professional code from amateur scripts.
Core Concepts: Understanding Exceptions π
What Is an Exception?
An exception is an event that disrupts the normal flow of a program's execution. When an exceptional condition occurs, Java creates an exception object containing information about the error, including its type and the state of the program when the error occurred.
Think of exceptions like emergency protocols in a building π’. Normal operations follow the standard workflow, but when a fire alarm triggers, special emergency procedures take over. Similarly, when an exception occurs, Java interrupts normal execution and follows special exception-handling procedures.
The Exception Hierarchy π³
All Java exceptions inherit from the Throwable class. Understanding this hierarchy is crucial:
Throwable
β
βββββββββββββ΄ββββββββββββ
β β
Error Exception
β β
(VirtualMachineError) βββββββ΄ββββββ
(OutOfMemoryError) β β
... RuntimeException IOException
β SQLException
ββββββββ΄βββββββ ...
β β
NullPointerException ArithmeticException
IndexOutOfBoundsException
...
Key distinction:
- Error: Serious problems that applications shouldn't try to catch (e.g.,
OutOfMemoryError) - Exception: Conditions that applications should catch
- Checked exceptions: Must be declared or caught (e.g.,
IOException,SQLException) - Unchecked exceptions: Runtime exceptions that don't require explicit handling (e.g.,
NullPointerException,ArithmeticException)
- Checked exceptions: Must be declared or caught (e.g.,
Checked vs Unchecked Exceptions βοΈ
| Aspect | Checked Exceptions | Unchecked Exceptions |
|---|---|---|
| Inheritance | Extend Exception (but not RuntimeException) |
Extend RuntimeException |
| Compiler Check | β Must handle or declare | β No compile-time enforcement |
| Use Case | Recoverable conditions (file not found, network timeout) | Programming errors (null pointer, array index) |
| Examples | IOException, SQLException, ClassNotFoundException |
NullPointerException, IllegalArgumentException, ArithmeticException |
π‘ Memory Aid - "CHECK before you WRECK":
- CHECKed exceptions = external problems you should CHECK for (files, networks, databases)
- UnCHECKed exceptions = internal WRECKs caused by programming mistakes
The Try-Catch-Finally Pattern π―
The fundamental structure for handling exceptions:
try {
// Code that might throw an exception
riskyOperation();
} catch (SpecificException e) {
// Handle specific exception type
handleError(e);
} catch (AnotherException e) {
// Handle another exception type
handleDifferently(e);
} finally {
// Always executes (cleanup code)
cleanup();
}
Execution flow:
βββββββββββββββββββββββββββββββββββββββββββ
β START: Enter try block β
ββββββββββββββββ¬βββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββ
β Exception β
β thrown? β
βββββ¬βββββββββ¬ββββ
β β
NO β β YES
β β
βΌ βΌ
ββββββββββββ ββββββββββββββββββββ
β Skip β β Find matching β
β catch β β catch block β
β blocks β ββββββββββ¬ββββββββββ
βββββββ¬βββββ β
β βΌ
β ββββββββββββββββ
β β Execute β
β β catch block β
β ββββββββ¬ββββββββ
β β
ββββββββββ¬ββββββββ
β
βΌ
ββββββββββββββββ
β Execute β
β finally β
β (always) β
ββββββββ¬ββββββββ
β
βΌ
ββββββββββββββββ
β Continue β
β program β
ββββββββββββββββ
β οΈ Critical Rule: The finally block always executes, even if:
- No exception occurs
- An exception is caught
- An exception is not caught (before propagating up)
- A
returnstatement is in thetryorcatchblock
The only ways finally doesn't execute: System.exit() is called, or the JVM crashes.
The Try-Catch Mechanism in Detail π§
Basic Exception Catching
public class BasicExceptionExample {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid array index: " + e.getMessage());
System.out.println("Array length was exceeded");
}
System.out.println("Program continues normally");
}
}
Output:
Invalid array index: Index 5 out of bounds for length 3
Array length was exceeded
Program continues normally
Without the try-catch, the program would crash at the array access, and the last print statement would never execute.
Multiple Catch Blocks
You can catch different exception types with separate handlers:
public void processFile(String filename) {
try {
FileReader reader = new FileReader(filename);
int data = reader.read();
int result = 100 / data; // Could divide by zero
reader.close();
} catch (FileNotFoundException e) {
System.err.println("File not found: " + filename);
logError(e);
} catch (IOException e) {
System.err.println("Error reading file: " + e.getMessage());
logError(e);
} catch (ArithmeticException e) {
System.err.println("Cannot divide by zero");
}
}
π‘ Order matters! Catch blocks are checked sequentially. Always put more specific exceptions before more general ones:
// β WRONG - Won't compile!
try {
// code
} catch (Exception e) { // Too general - catches everything
// handle
} catch (IOException e) { // Unreachable! Already caught above
// never executes
}
// β
RIGHT
try {
// code
} catch (FileNotFoundException e) { // Most specific
// handle
} catch (IOException e) { // More general
// handle
} catch (Exception e) { // Most general
// handle
}
Multi-Catch (Java 7+)
When multiple exceptions need the same handling:
try {
// code that might throw different exceptions
performOperation();
} catch (IOException | SQLException | TimeoutException e) {
// Handle all three the same way
logger.error("Operation failed: " + e.getMessage());
notifyAdmin(e);
}
β οΈ Restriction: In a multi-catch, the exception variable e is implicitly finalβyou cannot reassign it.
The Finally Block - Guaranteed Cleanup π§Ή
The finally block is essential for cleanup operations that must happen regardless of success or failure:
public String readFile(String path) throws IOException {
FileReader reader = null;
try {
reader = new FileReader(path);
// Read file contents
return readContents(reader);
} catch (FileNotFoundException e) {
System.err.println("File not found: " + path);
throw e; // Re-throw
} finally {
// This ALWAYS runs, even if we return or throw above
if (reader != null) {
try {
reader.close(); // Release resource
} catch (IOException e) {
System.err.println("Failed to close reader");
}
}
}
}
Common finally uses:
- Closing file handles, database connections, network sockets
- Releasing locks
- Restoring state
- Logging completion
Try-With-Resources (Java 7+) π
A cleaner way to handle resources that implement AutoCloseable:
// Old way - verbose
public void oldWay(String path) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
System.out.println(line);
} finally {
if (reader != null) {
reader.close();
}
}
}
// New way - automatic resource management
public void newWay(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line = reader.readLine();
System.out.println(line);
} // reader.close() called automatically!
}
The try-with-resources statement:
- Automatically closes resources when the try block exits
- Resources are closed in the reverse order of their creation
- Can declare multiple resources separated by semicolons
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = br.readLine()) != null) {
fos.write(line.getBytes());
}
} // All three resources closed automatically in reverse order
Throwing Exceptions πΎ
The Throw Statement
You can explicitly throw exceptions using the throw keyword:
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException(
"Age must be between 0 and 150, got: " + age
);
}
this.age = age;
}
Throwing process:
ββββββββββββββββββββββββββββββββββββββββββ
β Method encounters throw statement β
ββββββββββββββββ¬ββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββ
β Create exception object β
β - Set message β
β - Capture stack trace β
ββββββββββββββββ¬ββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββ
β Stop normal execution β
β Jump to nearest catch block β
ββββββββββββββββ¬ββββββββββββββββββββββββββ
β
ββββββββ΄βββββββ
β β
Caught Not caught
β β
βΌ βΌ
Handle Propagate up
exception call stack
The Throws Clause
When a method might throw a checked exception but doesn't handle it, you must declare it with throws:
public String readFirstLine(String filename) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filename));
return reader.readLine();
}
// Caller must handle IOException or declare throws IOException
Multiple exceptions:
public void connectToDatabase(String url)
throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.jdbc.Driver"); // ClassNotFoundException
Connection conn = DriverManager.getConnection(url); // SQLException
}
π‘ Design principle: Declare throws for exceptions that callers should be aware of and might want to handle differently.
Exception Chaining and Wrapping π
Capture low-level exceptions and re-throw as higher-level ones:
public User loadUser(int userId) throws DataAccessException {
try {
// Low-level database code
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setInt(1, userId);
ResultSet rs = stmt.executeQuery();
return mapToUser(rs);
} catch (SQLException e) {
// Wrap low-level SQLException in high-level exception
throw new DataAccessException(
"Failed to load user: " + userId,
e // Original exception preserved as cause
);
}
}
Benefits:
- Abstracts implementation details
- Preserves full stack trace via
getCause() - Allows different layers to use appropriate exception types
try {
User user = userService.loadUser(123);
} catch (DataAccessException e) {
System.err.println("Service error: " + e.getMessage());
System.err.println("Root cause: " + e.getCause());
e.printStackTrace(); // Shows complete chain
}
Creating Custom Exceptions π¨
Custom exceptions make your code more expressive and domain-specific:
// Custom checked exception
public class InsufficientFundsException extends Exception {
private final double balance;
private final double requestedAmount;
public InsufficientFundsException(double balance, double requested) {
super(String.format(
"Insufficient funds: balance=%.2f, requested=%.2f",
balance, requested
));
this.balance = balance;
this.requestedAmount = requested;
}
public double getBalance() {
return balance;
}
public double getShortfall() {
return requestedAmount - balance;
}
}
// Usage
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(balance, amount);
}
balance -= amount;
}
Custom unchecked exception:
public class InvalidConfigurationException extends RuntimeException {
private final String configKey;
public InvalidConfigurationException(String key, String message) {
super("Invalid configuration for '" + key + "': " + message);
this.configKey = key;
}
public InvalidConfigurationException(String key, String message, Throwable cause) {
super("Invalid configuration for '" + key + "': " + message, cause);
this.configKey = key;
}
public String getConfigKey() {
return configKey;
}
}
π§ Naming convention: Exception class names should end with "Exception"
Best practices for custom exceptions:
β Do's
- Extend
Exceptionfor checked exceptions - Extend
RuntimeExceptionfor unchecked exceptions - Provide multiple constructors (message, cause, both)
- Add relevant fields and getters for exception context
- Include meaningful error messages
- Document when the exception is thrown
β Don'ts
- Don't create exception classes unnecessarilyβuse standard ones when appropriate
- Don't expose internal implementation details in exception messages
- Don't include sensitive data (passwords, tokens) in exception messages
- Don't catch exceptions just to wrap them without adding value
Real-World Examples π
Example 1: File Processing with Robust Error Handling
public class FileProcessor {
private static final Logger logger = Logger.getLogger(FileProcessor.class.getName());
public List<String> processDataFile(String filepath) {
List<String> results = new ArrayList<>();
int lineNumber = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
String line;
while ((line = reader.readLine()) != null) {
lineNumber++;
try {
String processed = processLine(line);
results.add(processed);
} catch (ParseException e) {
// Log error but continue processing other lines
logger.warning(String.format(
"Skipping line %d due to parse error: %s",
lineNumber, e.getMessage()
));
}
}
logger.info(String.format(
"Successfully processed %d lines from %s",
results.size(), filepath
));
} catch (FileNotFoundException e) {
logger.severe("File not found: " + filepath);
throw new IllegalArgumentException("Invalid file path: " + filepath, e);
} catch (IOException e) {
logger.severe("IO error reading file: " + e.getMessage());
throw new RuntimeException("Failed to read file: " + filepath, e);
}
return results;
}
private String processLine(String line) throws ParseException {
if (line.trim().isEmpty()) {
throw new ParseException("Empty line", 0);
}
// Process line...
return line.toUpperCase();
}
}
Key techniques demonstrated:
- Try-with-resources for automatic file closure
- Nested try-catch for line-level error handling
- Continue processing despite individual failures
- Logging at appropriate levels
- Wrapping low-level exceptions with context
Example 2: Database Transaction with Rollback
public class BankService {
private final DataSource dataSource;
public void transferMoney(int fromAccount, int toAccount, double amount)
throws TransferException {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // Start transaction
// Withdraw from source account
if (!withdraw(conn, fromAccount, amount)) {
throw new InsufficientFundsException(
getBalance(conn, fromAccount), amount
);
}
// Deposit to destination account
if (!deposit(conn, toAccount, amount)) {
throw new TransferException("Failed to deposit funds");
}
conn.commit(); // Success - commit transaction
} catch (SQLException e) {
// Database error - rollback transaction
rollback(conn);
throw new TransferException("Database error during transfer", e);
} catch (InsufficientFundsException e) {
// Business rule violation - rollback
rollback(conn);
throw new TransferException("Insufficient funds", e);
} catch (Exception e) {
// Unexpected error - rollback and wrap
rollback(conn);
throw new TransferException("Unexpected error during transfer", e);
} finally {
// Always close connection
closeConnection(conn);
}
}
private void rollback(Connection conn) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e) {
// Log but don't throw - we're already handling an exception
logger.error("Failed to rollback transaction", e);
}
}
}
private void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error("Failed to close connection", e);
}
}
}
}
Key techniques:
- Transaction management with commit/rollback
- Multiple catch blocks for different error types
- Safe cleanup methods that don't throw
- Chaining exceptions to preserve context
Example 3: Retry Logic with Exception Handling
public class NetworkClient {
private static final int MAX_RETRIES = 3;
private static final long RETRY_DELAY_MS = 1000;
public String fetchData(String url) throws NetworkException {
int attempts = 0;
Exception lastException = null;
while (attempts < MAX_RETRIES) {
try {
attempts++;
return performRequest(url);
} catch (SocketTimeoutException e) {
// Timeout - worth retrying
lastException = e;
logger.warning(String.format(
"Request timeout (attempt %d/%d): %s",
attempts, MAX_RETRIES, url
));
if (attempts < MAX_RETRIES) {
sleep(RETRY_DELAY_MS * attempts); // Exponential backoff
}
} catch (UnknownHostException e) {
// DNS failure - no point retrying immediately
throw new NetworkException("Unknown host: " + url, e);
} catch (IOException e) {
// Other IO error - retry
lastException = e;
logger.warning(String.format(
"IO error (attempt %d/%d): %s",
attempts, MAX_RETRIES, e.getMessage()
));
if (attempts < MAX_RETRIES) {
sleep(RETRY_DELAY_MS);
}
}
}
// All retries exhausted
throw new NetworkException(
String.format("Failed after %d attempts: %s", MAX_RETRIES, url),
lastException
);
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Interrupted during retry delay", e);
}
}
}
Key techniques:
- Retry logic with exponential backoff
- Different handling for different exception types
- Preserving the last exception for final throw
- Proper interrupt handling
Example 4: Validation with Multiple Exception Types
public class UserRegistrationService {
public void registerUser(String email, String password, int age)
throws ValidationException {
List<String> errors = new ArrayList<>();
// Validate email
try {
validateEmail(email);
} catch (InvalidEmailException e) {
errors.add(e.getMessage());
}
// Validate password
try {
validatePassword(password);
} catch (WeakPasswordException e) {
errors.add(e.getMessage());
}
// Validate age
try {
validateAge(age);
} catch (InvalidAgeException e) {
errors.add(e.getMessage());
}
// If any validation failed, throw exception with all errors
if (!errors.isEmpty()) {
throw new ValidationException(
"User validation failed",
errors
);
}
// All validations passed - proceed with registration
saveUser(email, password, age);
}
private void validateEmail(String email) throws InvalidEmailException {
if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$")) {
throw new InvalidEmailException("Invalid email format: " + email);
}
}
private void validatePassword(String password) throws WeakPasswordException {
if (password == null || password.length() < 8) {
throw new WeakPasswordException("Password must be at least 8 characters");
}
if (!password.matches(".*[A-Z].*")) {
throw new WeakPasswordException("Password must contain uppercase letter");
}
if (!password.matches(".*[0-9].*")) {
throw new WeakPasswordException("Password must contain a digit");
}
}
private void validateAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("Must be 18 or older");
}
if (age > 120) {
throw new InvalidAgeException("Invalid age: " + age);
}
}
}
// Custom exception that collects multiple errors
public class ValidationException extends Exception {
private final List<String> errors;
public ValidationException(String message, List<String> errors) {
super(message + ": " + String.join("; ", errors));
this.errors = new ArrayList<>(errors);
}
public List<String> getErrors() {
return Collections.unmodifiableList(errors);
}
}
Key techniques:
- Collecting multiple validation errors before throwing
- Specific exception types for different validation failures
- Providing detailed feedback to users
Common Mistakes and How to Avoid Them β οΈ
Mistake 1: Swallowing Exceptions
// β WRONG - Silent failure
try {
dangerousOperation();
} catch (Exception e) {
// Do nothing - exception disappears!
}
// β WRONG - Useless catch
try {
dangerousOperation();
} catch (Exception e) {
e.printStackTrace(); // Only during development!
}
// β
RIGHT - Proper handling
try {
dangerousOperation();
} catch (Exception e) {
logger.error("Operation failed", e);
// Either recover, or rethrow wrapped exception
throw new RuntimeException("Failed to complete operation", e);
}
Why it's wrong: Silent failures make debugging impossible. You'll never know why something failed.
Mistake 2: Catching Too Broadly
// β WRONG - Catches everything, including programming errors
try {
processData();
calculateResults();
saveToDatabase();
} catch (Exception e) {
// This catches NullPointerException, OutOfMemoryError, etc!
logger.error("Something went wrong", e);
}
// β
RIGHT - Catch specific exceptions
try {
processData();
calculateResults();
saveToDatabase();
} catch (IOException e) {
// Handle I/O problems
logger.error("I/O error", e);
} catch (SQLException e) {
// Handle database problems
logger.error("Database error", e);
}
// Let NullPointerException and other bugs crash - you want to know about them!
Mistake 3: Improper Finally Usage
// β WRONG - Return in finally overrides try/catch return
public String readFile() {
try {
return "data from file";
} finally {
return "finally"; // This overwrites the try return!
}
// Always returns "finally", never "data from file"
}
// β WRONG - Throwing in finally masks original exception
public void processFile() throws IOException {
try {
throw new IOException("File error");
} finally {
throw new RuntimeException("Finally error");
// IOException is lost! RuntimeException thrown instead
}
}
// β
RIGHT - Only cleanup in finally
public String readFile() throws IOException {
Reader reader = null;
try {
reader = new FileReader("file.txt");
return readData(reader);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.warn("Failed to close reader", e);
// Don't rethrow - we're cleaning up
}
}
}
}
Mistake 4: Logging and Rethrowing
// β WRONG - Creates duplicate log entries
public void method1() {
try {
method2();
} catch (IOException e) {
logger.error("Error in method1", e); // Logged here
throw new RuntimeException("Failed", e);
}
}
public void method2() throws IOException {
try {
readFile();
} catch (IOException e) {
logger.error("Error in method2", e); // Also logged here!
throw e;
}
}
// Same exception logged twice with full stack trace
// β
RIGHT - Log once at the boundary
public void method1() {
try {
method2();
} catch (IOException e) {
logger.error("Operation failed", e); // Log once
// Handle or convert to appropriate exception type
throw new RuntimeException("Failed", e);
}
}
public void method2() throws IOException {
readFile(); // Let exception propagate
}
Mistake 5: Exception for Control Flow
// β WRONG - Using exceptions for normal logic
public boolean contains(List<String> list, String item) {
try {
int index = list.indexOf(item);
String found = list.get(index);
return true;
} catch (IndexOutOfBoundsException e) {
return false;
}
}
// β
RIGHT - Use normal conditional logic
public boolean contains(List<String> list, String item) {
return list.contains(item);
}
// β WRONG - Exception to break loop
while (true) {
try {
String item = iterator.next();
process(item);
} catch (NoSuchElementException e) {
break; // Use exception to exit loop
}
}
// β
RIGHT - Use proper loop condition
while (iterator.hasNext()) {
String item = iterator.next();
process(item);
}
Why it's wrong: Exceptions are expensive (stack trace creation), and they obscure program logic.
Mistake 6: Not Providing Context
// β WRONG - No context
if (age < 0) {
throw new IllegalArgumentException("Invalid age");
}
// β
RIGHT - Include relevant values
if (age < 0) {
throw new IllegalArgumentException(
"Age must be non-negative, but was: " + age
);
}
// β WRONG - Rethrow without adding context
try {
processUser(userId);
} catch (DataAccessException e) {
throw e; // Lost information about what we were trying to do
}
// β
RIGHT - Add context when rethrowing
try {
processUser(userId);
} catch (DataAccessException e) {
throw new UserProcessingException(
"Failed to process user ID: " + userId,
e
);
}
Best Practices Summary π
π― Exception Handling Best Practices
| Principle | Guideline |
|---|---|
| Be Specific | Catch specific exceptions, not Exception or Throwable |
| Fail Fast | Validate inputs early and throw exceptions immediately |
| Preserve Context | Always pass the original exception as cause when wrapping |
| Document | Use @throws Javadoc for all checked exceptions |
| Clean Up | Use try-with-resources or finally for resource cleanup |
| Don't Swallow | Never catch and ignore exceptions without good reason |
| Log Appropriately | Log exceptions once at the appropriate level |
| Check vs Uncheck | Use checked for recoverable conditions, unchecked for programming errors |
Key Takeaways π
Exception Hierarchy: All exceptions inherit from
Throwable. Understand the difference betweenError, checkedException, andRuntimeException.Checked vs Unchecked: Checked exceptions must be handled or declared; unchecked exceptions indicate programming errors.
Try-Catch-Finally: The fundamental pattern for exception handling. Finally always executes, even with return statements.
Try-With-Resources: Modern approach for automatic resource management with
AutoCloseableresources.Throw and Throws:
throwraises an exception;throwsdeclares that a method might throw checked exceptions.Custom Exceptions: Create domain-specific exceptions by extending
Exception(checked) orRuntimeException(unchecked).Exception Chaining: Preserve original exception context when wrapping low-level exceptions.
Don't Swallow: Never catch exceptions without proper handling or logging.
Be Specific: Catch specific exception types and order them from most to least specific.
Resource Cleanup: Always clean up resources in
finallyblocks or use try-with-resources.
Quick Reference Card π
π» Exception Handling Cheat Sheet
| Pattern | Syntax | Use Case |
|---|---|---|
| Basic try-catch | try { } catch (E e) { } |
Handle specific exception |
| Multi-catch | catch (E1 | E2 e) { } |
Same handler for multiple types |
| Finally | try { } finally { } |
Guaranteed cleanup code |
| Try-with-resources | try (R r = ...) { } |
Auto-close AutoCloseable resources |
| Throw | throw new E(msg); |
Raise exception |
| Throws | void m() throws E { } |
Declare checked exception |
| Custom checked | class E extends Exception { } |
Domain-specific recoverable error |
| Custom unchecked | class E extends RuntimeException { } |
Domain-specific programming error |
| Wrap exception | throw new E(msg, cause); |
Preserve original exception |
π Common Exception Types
| Exception | Type | When It Occurs |
|---|---|---|
NullPointerException |
Unchecked | Accessing member of null reference |
ArrayIndexOutOfBoundsException |
Unchecked | Invalid array index |
IllegalArgumentException |
Unchecked | Invalid method argument |
IllegalStateException |
Unchecked | Method called at wrong time |
IOException |
Checked | I/O operation failure |
SQLException |
Checked | Database operation failure |
ClassNotFoundException |
Checked | Class not found at runtime |
InterruptedException |
Checked | Thread interrupted |
π Further Study
Deepen your understanding of exception handling:
Oracle's Exception Handling Tutorial: https://docs.oracle.com/javase/tutorial/essential/exceptions/
- Official comprehensive guide from Oracle covering all aspects of Java exception handling
Effective Java by Joshua Bloch - Exception Chapter: https://www.oreilly.com/library/view/effective-java/9780134686097/
- Industry-standard best practices for exception handling (Items 69-77)
Baeldung's Exception Handling Guide: https://www.baeldung.com/java-exceptions
- Practical examples and modern patterns for exception handling in Java applications