Index

Exception Handling Syntax

Java Syntax

13.1 try, catch, and finally

Exception handling is a core feature in Java that helps manage runtime errors gracefully, preventing program crashes and enabling more robust code. The basic structure involves three key blocks: try, catch, and finally.

The Basic Structure

Syntax Example

try {
    // Code that may throw an exception
} catch (ExceptionType1 e1) {
    // Handle ExceptionType1
} catch (ExceptionType2 e2) {
    // Handle ExceptionType2
} finally {
    // Cleanup code, always executed
}

Practical Example: Division by Zero

Consider a program that divides two integers entered by the user. Dividing by zero throws an ArithmeticException, which we can catch and handle:

public class DivisionExample {
    public static void main(String[] args) {
        int numerator = 10;
        int denominator = 0;
        try {
            int result = numerator / denominator;  // This throws ArithmeticException
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Error: Cannot divide by zero.");
        } finally {
            System.out.println("Execution of try-catch-finally block completed.");
        }
        System.out.println("Program continues...");
    }
}

Output:

Error: Cannot divide by zero.
Execution of try-catch-finally block completed.
Program continues...

Flow Explanation

Another Example: Array Access

Exception handling is also useful for handling invalid array indices:

try {
    int[] numbers = {1, 2, 3};
    System.out.println(numbers[5]);  // ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Invalid array index accessed.");
} finally {
    System.out.println("Cleanup or final operations go here.");
}

This prevents the program from terminating unexpectedly and allows graceful recovery.

Why Use Exception Handling?

Key Points About finally

Reflection

Exception handling is fundamental in building reliable Java applications. By anticipating errors and preparing responses, you can maintain control flow integrity and avoid unexpected failures. The combination of try, catch, and finally blocks not only safeguards program execution but also fosters cleaner, more maintainable code. This structure allows developers to separate normal logic from error recovery and cleanup, which ultimately leads to more robust and professional-grade applications.

Index

13.2 Multiple Catch Blocks

Java's exception handling mechanism allows you to catch and handle different types of exceptions that might be thrown in your program. Using multiple catch blocks enables fine-grained control over how various exceptions are handled, improving program robustness and clarity.

Basic Multiple Catch Blocks

You can write several catch blocks after a single try block, each tailored to handle a specific type of exception. The Java runtime will execute the first catch block with an exception type matching the thrown exception.

try {
    // Code that may throw exceptions
} catch (FileNotFoundException e) {
    System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {
    System.out.println("I/O error: " + e.getMessage());
} catch (Exception e) {
    System.out.println("General error: " + e.getMessage());
}

In this example:

Ordering of Catch Blocks: Most Specific First

It is important to order your catch blocks from most specific to most general. This ensures that exceptions are caught by the most appropriate handler.

Why?

If a general exception type (like Exception) is caught before a more specific one (like FileNotFoundException), the more specific block becomes unreachable, leading to a compile-time error.

Example of incorrect order causing compile error:

try {
    // ...
} catch (Exception e) {  // Catches all exceptions first
    // ...
} catch (IOException e) {  // Unreachable: compile error
    // ...
}

The compiler prevents this because the second block can never be reached.

Multi-Catch Syntax (catch (IOException SQLException e))

Introduced in Java 7, multi-catch lets you handle multiple exception types in a single catch block using the pipe (|) operator:

try {
    // Code that may throw IOException or SQLException
} catch (IOException | SQLException e) {
    System.out.println("Error occurred: " + e.getMessage());
}

Benefits:

Restrictions:

Practical Example

import java.io.*;
import java.sql.*;

public class MultiCatchExample {
    public static void main(String[] args) {
        try {
            readFile("data.txt");
            queryDatabase("SELECT * FROM users");
        } catch (FileNotFoundException e) {
            System.out.println("File missing: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("I/O error: " + e.getMessage());
        } catch (SQLException e) {
            System.out.println("Database error: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Unexpected error: " + e.getMessage());
        }
    }

    static void readFile(String filename) throws IOException {
        // Simulate file operation
        throw new FileNotFoundException("File " + filename + " not found.");
    }

    static void queryDatabase(String query) throws SQLException {
        // Simulate database operation
        throw new SQLException("Invalid SQL query.");
    }
}

Here, exceptions are handled with granularity, allowing different messages based on the exact error type.

Using multi-catch, the above could be simplified if FileNotFoundException and IOException were handled the same way:

try {
    readFile("data.txt");
    queryDatabase("SELECT * FROM users");
} catch (FileNotFoundException | SQLException e) {
    System.out.println("Error: " + e.getMessage());
} catch (IOException e) {
    System.out.println("I/O error: " + e.getMessage());
} catch (Exception e) {
    System.out.println("Unexpected error: " + e.getMessage());
}

Reflection: Why Use Multiple Catch Blocks?

Summary

Multiple catch blocks and multi-catch syntax enhance Java's exception handling by offering:

Properly ordering catch blocks and using multi-catch where appropriate are best practices that help produce reliable, maintainable, and clear Java applications.

Index

13.3 throw and throws

Exception handling in Java not only involves catching exceptions but also explicitly raising (throwing) them and declaring which exceptions a method might propagate. The keywords throw and throws serve these purposes.

The throw Statement: Manually Raising Exceptions

The throw keyword is used inside a method or block to manually raise an exception. When a throw statement executes, it immediately stops the current flow and passes control to the nearest matching catch block or propagates the exception up the call stack.

Syntax:

throw new ExceptionType("Error message");

Example:

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative");
    }
    this.age = age;
}

In this example, if an invalid age is passed, the method explicitly throws an IllegalArgumentException, signaling a problem to the caller.

The throws Clause: Declaring Exceptions in Method Signatures

The throws keyword is used in a method declaration to indicate that the method might throw one or more exceptions. This informs callers that they need to handle or further declare these exceptions.

Syntax:

public void methodName() throws IOException, SQLException {
    // method code that might throw IOException or SQLException
}

Example with checked exceptions:

public void readFile(String filename) throws IOException {
    FileReader file = new FileReader(filename); // may throw FileNotFoundException (subclass of IOException)
    // Reading logic...
}

Here, readFile declares that it throws IOException. Any code that calls readFile must either handle the exception with a try-catch block or declare it further up.

Checked vs. Unchecked Exceptions

Java distinguishes between checked and unchecked exceptions, which affects how throw and throws are used.

Implications:

Why Use throw and throws?

Practical Example Combining throw and throws

import java.io.*;

public class FileProcessor {

    // Declare that this method throws IOException (checked)
    public void processFile(String filename) throws IOException {
        if (filename == null || filename.isEmpty()) {
            // Throw unchecked exception: IllegalArgumentException
            throw new IllegalArgumentException("Filename cannot be null or empty");
        }
        FileReader reader = new FileReader(filename); // May throw FileNotFoundException
        // Read and process file...
    }

    public static void main(String[] args) {
        FileProcessor processor = new FileProcessor();

        try {
            processor.processFile("");
        } catch (IllegalArgumentException e) {
            System.out.println("Invalid argument: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("I/O failure: " + e.getMessage());
        }
    }
}

Explanation:

Reflection on Exception Handling Design

The separation between throw and throws along with checked and unchecked exceptions is a key design aspect of Java's error handling:

Summary

Index

13.4 Creating Custom Exceptions

In Java, while the standard exception classes cover many common error conditions, sometimes your application requires custom exceptions to represent specific problems unique to your domain or business logic. Custom exceptions improve clarity, enable more precise error handling, and make your code more expressive.

Defining a Custom Exception Class

To create a custom exception, you define a new class that extends either:

Basic syntax:

// Checked exception
public class InvalidUserInputException extends Exception {
    public InvalidUserInputException(String message) {
        super(message);
    }
}

// Unchecked exception
public class InsufficientBalanceException extends RuntimeException {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

These classes typically provide constructors that pass a message or cause to the superclass, making them easy to instantiate and throw with informative error details.

Example: Throwing and Catching a Custom Exception

Let's create a simple banking example where withdrawing more money than available balance raises a custom unchecked exception:

// Custom unchecked exception
public class InsufficientBalanceException extends RuntimeException {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(double amount) {
        if (amount > balance) {
            throw new InsufficientBalanceException(
                "Withdrawal amount exceeds available balance"
            );
        }
        balance -= amount;
    }

    public double getBalance() {
        return balance;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        try {
            account.withdraw(1500);
        } catch (InsufficientBalanceException e) {
            System.out.println("Transaction failed: " + e.getMessage());
        }
    }
}

What happens here?

Checked vs. Unchecked Custom Exceptions

You can create either checked or unchecked custom exceptions depending on your error-handling needs:

For example, if invalid user input is recoverable, you might create:

public class InvalidUserInputException extends Exception {
    public InvalidUserInputException(String message) {
        super(message);
    }
}

And force callers to handle or propagate it.

Why Create Custom Exceptions?

Custom exceptions are not just a formalismβ€”they make your business logic more meaningful and expressive:

Best Practices for Custom Exceptions

Summary

Index