Index

Exception Handling

Java for Beginners

9.1 try, catch, finally

In Java, exceptions represent unexpected events or errors that occur during program execution, such as trying to divide by zero or accessing a file that doesn’t exist. To gracefully handle these situations without crashing, Java provides the try-catch-finally mechanism, allowing you to catch and manage errors while maintaining program control.

The try Block

The try block contains code that might throw an exception. It’s the “risky” section where something could go wrong.

try {
    int result = 10 / 0;  // This will throw ArithmeticException
}

If an exception occurs inside the try block, Java immediately stops executing that block and looks for an appropriate catch block to handle it.

The catch Block

The catch block handles exceptions thrown in the corresponding try block. You can specify the type of exception you want to catch.

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Cannot divide by zero!");
}

You can also catch multiple exception types by adding multiple catch blocks:

try {
    String text = null;
    System.out.println(text.length());  // Throws NullPointerException
} catch (ArithmeticException e) {
    System.out.println("Math error: " + e.getMessage());
} catch (NullPointerException e) {
    System.out.println("Null reference detected!");
}

The finally Block

The finally block contains code that always runs after the try and catch blocks, regardless of whether an exception occurred or not. It’s commonly used for cleaning up resources like closing files or database connections.

try {
    System.out.println("Trying risky operation");
} catch (Exception e) {
    System.out.println("Exception handled");
} finally {
    System.out.println("This runs no matter what");
}

Even if you return from the try or catch block, the finally block will still execute.

Nested try Blocks

You can nest try-catch blocks inside one another to handle exceptions at different granularities:

try {
    try {
        int[] numbers = {1, 2};
        System.out.println(numbers[5]);  // Throws ArrayIndexOutOfBoundsException
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("Inner catch: Invalid index");
    }
    int a = 10 / 0;  // Throws ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("Outer catch: Division by zero");
}
Click to view full runnable Code

public class Main {
    public static void main(String[] args) {
        try {
            try {
                int[] numbers = {1, 2};
                System.out.println(numbers[5]);  // Throws ArrayIndexOutOfBoundsException
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Inner catch: Invalid index");
            }
            int a = 10 / 0;  // Throws ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Outer catch: Division by zero");
        }
    }
}

Best Practices

// Bad: silently ignores exceptions
catch (Exception e) {
    // nothing here
}
catch (Exception e) {
    e.printStackTrace();
}

Complete Example

import java.io.*;

public class ExceptionDemo {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("file.txt"));
            String line = reader.readLine();
            System.out.println(line);
        } catch (FileNotFoundException e) {
            System.out.println("File not found!");
        } catch (IOException e) {
            System.out.println("I/O error occurred");
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                    System.out.println("Reader closed");
                }
            } catch (IOException e) {
                System.out.println("Failed to close reader");
            }
        }
    }
}

This example opens a file, reads a line, and handles possible exceptions while ensuring the file resource is closed in the finally block.

Summary

Understanding and properly using try-catch-finally is essential for writing reliable Java programs that can gracefully recover from errors without crashing.

Index

9.2 Throwing and Catching Exceptions

In Java, you don’t just wait for exceptions to happen—you can explicitly throw them yourself using the throw keyword. This allows you to enforce rules, validate inputs, and signal problems in your code clearly. In this section, we’ll explore how to throw exceptions, catch them, and keep your program stable.

Throwing Exceptions with throw

The throw statement lets you create and throw an exception manually. For example, if a method receives an invalid argument, you can throw an IllegalArgumentException:

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

When throw is executed, the normal program flow stops, and Java looks for a matching catch block.

Checked vs Unchecked Exceptions (Brief Overview)

In the example above, IllegalArgumentException is an unchecked exception, so you don’t have to declare it in the method signature.

Catching Thrown Exceptions

To handle exceptions thrown by your code or others, use a try-catch block:

try {
    setAge(-5);
} catch (IllegalArgumentException e) {
    System.out.println("Error: " + e.getMessage());
}

This catches the exception thrown by setAge() and prints an error message, preventing the program from crashing.

Complete Example: Input Validation with Exception Throwing and Catching

import java.util.Scanner;

public class AgeValidator {

    public static void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        System.out.println("Age set to " + age);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("Enter your age: ");
        int inputAge = scanner.nextInt();

        try {
            setAge(inputAge);
        } catch (IllegalArgumentException e) {
            System.out.println("Caught exception: " + e.getMessage());
            System.out.println("Please enter a valid age between 0 and 150.");
        }

        scanner.close();
    }
}

Explanation:

Summary

Mastering throwing and catching exceptions is essential to writing reliable Java programs that handle errors gracefully and communicate problems clearly.

Index

9.3 Creating Custom Exceptions

Java provides many built-in exceptions, but sometimes you need to represent domain-specific errors that are not covered by the standard ones. In these cases, creating your own custom exceptions helps make your code clearer and more expressive.

Why Create Custom Exceptions?

How to Create a Custom Exception

Custom exceptions are classes that extend either:

Example: Creating an InvalidAgeException

Suppose your program needs to validate age input, and you want a custom exception to represent invalid age values:

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

Or, as an unchecked exception:

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

Throwing and Catching Custom Exceptions

Here is how you might use InvalidAgeException in your code:

public class User {
    private int age;

    // For checked exceptions, declare with throws
    public void setAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("Age " + age + " is not valid.");
        }
        this.age = age;
    }
}

To use this method and handle exceptions:

public class Main {
    public static void main(String[] args) {
        User user = new User();
        try {
            user.setAge(200);
        } catch (InvalidAgeException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

If InvalidAgeException extends RuntimeException, you can omit the throws declaration and try-catch block but still choose to catch it if desired.

Click to view full runnable Code

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

class User {
    private int age;

    public void setAge(int age) throws InvalidAgeException {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("Age " + age + " is not valid.");
        }
        this.age = age;
    }
}

public class Main {
    public static void main(String[] args) {
        User user = new User();
        try {
            user.setAge(200);
        } catch (InvalidAgeException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

Benefits of Custom Exceptions

Best Practices for Custom Exceptions

public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
    public InvalidAgeException(String message, Throwable cause) {
        super(message, cause);
    }
}

Summary

Creating custom exceptions allows you to design error handling that fits your program’s needs perfectly. They provide a way to:

By defining, throwing, and catching custom exceptions, your Java applications become easier to maintain and more robust.

Index

9.4 Checked vs Unchecked Exceptions

In Java, exceptions are broadly categorized into checked and unchecked exceptions. Understanding the difference is essential for writing robust programs that handle errors properly while keeping the code clean and maintainable.

What Are Checked Exceptions?

Checked exceptions are exceptions that the Java compiler forces you to handle explicitly. This means any method that can throw a checked exception must either:

If neither is done, your program will fail to compile.

Common Checked Exceptions

Example: Checked Exception Handling

import java.io.*;

public class CheckedExample {
    public static void readFile(String filename) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filename));
        System.out.println(reader.readLine());
        reader.close();
    }

    public static void main(String[] args) {
        try {
            readFile("data.txt");
        } catch (IOException e) {
            System.out.println("File error: " + e.getMessage());
        }
    }
}

Because readFile declares throws IOException, callers must handle or propagate it.

What Are Unchecked Exceptions?

Unchecked exceptions are exceptions that the compiler does not require you to handle or declare. They usually indicate programming errors or unexpected runtime conditions.

Unchecked exceptions are subclasses of RuntimeException.

Common Unchecked Exceptions

Example: Unchecked Exception

public class UncheckedExample {
    public static void divide(int a, int b) {
        int result = a / b;  // May throw ArithmeticException
        System.out.println("Result: " + result);
    }

    public static void main(String[] args) {
        divide(10, 0);  // Will throw exception at runtime
    }
}

The compiler won’t force you to catch ArithmeticException, but if it occurs, the program crashes unless handled.

Pros and Cons of Checked vs Unchecked Exceptions

Aspect Checked Exceptions Unchecked Exceptions
Compiler Enforcement Must be handled or declared No compile-time requirement
Typical Use Case Recoverable conditions (e.g., I/O errors) Programming errors (e.g., null pointer)
Verbosity Can lead to more verbose code due to mandatory handling Cleaner code, but can mask errors
Error Propagation Explicit propagation via method signatures Propagates unchecked, may cause runtime failure
Flexibility Safer in critical systems needing explicit error handling Faster development, but riskier if misused

When to Use Custom Checked or Unchecked Exceptions

Summary

By grasping checked vs unchecked exceptions, you’ll write Java programs that are both reliable and maintainable, handling errors effectively without cluttering your code.

Index