Java Programming Exception Handling Study Guide

Java Exception Handling - Complete Tutorial

Master Java exception handling: Learn try-catch-finally blocks, throw, throws, custom exceptions, exception hierarchy, and best practices for robust error management.

1. Introduction to Exception Handling

Exceptions represent error conditions that disrupt normal flow. Java uses try-catch-finally to handle errors without crashing silently.

  • Throwable is root of errors and exceptions
  • Checked exceptions must be handled or declared
  • Unchecked: RuntimeException and subclasses
  • Error types are serious JVM problems
Diagram of Java exception handling: try, catch, finally, throw, throws, and Throwable hierarchy
Exceptions: try risky code; catch handles types; finally cleanup; throw/throws for checked errors.

Exception tip

Catch specific types first; use finally to close files and streams.

Basic try-catch
public class ExceptionIntro {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        }
    }
}

2. Exception Hierarchy

All exceptions inherit from Throwable. Exception and Error branch below it; RuntimeException is the base for unchecked exceptions.

  • Exception — recoverable conditions
  • RuntimeException — programming bugs
  • Error — OutOfMemoryError, StackOverflowError
  • Catch specific types before general Exception

3. try-catch-finally Blocks

The try block holds code that may fail. One or more catch blocks handle specific exception types. The optional finally block always runs for cleanup (unless the JVM exits).

  • try — risky code that may throw
  • catch — handles a matching exception type
  • finally — cleanup; runs after try/catch
  • try-with-resources — auto-closes AutoCloseable resources
try-with-resources
import java.io.*;

public class TryResources {
    public static void main(String[] args) {
        try (FileReader fr = new FileReader("data.txt")) {
            System.out.println("Reading...");
        } catch (IOException e) {
            System.out.println("File error: " + e.getMessage());
        }
    }
}

4. Try with Multiple Catch Blocks

A single try block can have multiple catch blocks, each handling a different exception type. When an exception is thrown, Java checks catch blocks from top to bottom and runs the first matching handler only.

Basic syntax

Multiple catch blocks
public class MultipleCatchDemo {
    public static void main(String[] args) {
        String input = args.length > 0 ? args[0] : "10";

        try {
            int value = Integer.parseInt(input);
            int result = 100 / value;
            System.out.println("Result: " + result);
        } catch (NumberFormatException e) {
            System.out.println("Invalid number: " + input);
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        } catch (Exception e) {
            System.out.println("Unexpected error: " + e.getMessage());
        } finally {
            System.out.println("Cleanup complete");
        }
    }
}

Rules for multiple catch blocks

  • Specific before general — catch subclasses before superclasses (e.g. IOException before Exception)
  • Only one catch runs — the first matching block handles the exception; others are skipped
  • Unreachable catch — placing a parent type before a child type is a compile error
  • Same try, shared finally — one finally applies to all catch blocks in that try
Different handlers for different errors
import java.io.*;

public class FileReadDemo {
    public static void readFile(String path) {
        try {
            FileReader reader = new FileReader(path);
            char[] buffer = new char[1024];
            reader.read(buffer);
            reader.close();
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + path);
        } catch (IOException e) {
            System.out.println("I/O error while reading: " + e.getMessage());
        } catch (SecurityException e) {
            System.out.println("No permission to read file");
        }
    }
}

Catch order: subclass before superclass

Java requires catch blocks to be ordered from most specific to most general. Reversing the order causes a compile-time unreachable catch error.

Correct order
try {
    // risky code
} catch (ArrayIndexOutOfBoundsException e) {
    // specific: index out of range
} catch (IllegalArgumentException e) {
    // specific: bad argument
} catch (RuntimeException e) {
    // broader unchecked
} catch (Exception e) {
    // broadest recoverable
}
Compile error — wrong order
try {
    // risky code
} catch (Exception e) {
    // handles everything below Exception
} catch (IOException e) {
    // ERROR: Unreachable catch block for IOException
}

Multi-catch (Java 7+)

Use a single catch with the pipe (|) operator when two or more exception types need the same handling. The caught variable is implicitly final.

Multi-catch example
import java.io.*;
import java.sql.*;

public class MultiCatchDemo {
    public static void connect() {
        try {
            // code that may throw IOException or SQLException
        } catch (IOException | SQLException e) {
            System.out.println("Connection failed: " + e.getMessage());
            // e cannot be reassigned here
        }
    }
}
  • Syntax: catch (TypeA | TypeB e) { ... }
  • Cannot combine a type with its subclass: catch (Exception | IOException e) is invalid
  • The variable cannot be reassigned inside the catch block

Multiple catch vs multi-catch

ApproachWhen to useExample
Multiple catch blocksDifferent logic per exception typecatch (FileNotFoundException e) vs catch (IOException e)
Multi-catch (|)Same handling for several typescatch (IOException | SQLException e)
Single broad catch (Exception e)Top-level logging or fallback onlyAvoid when you can handle types separately

Quick tip

Prefer specific catches with meaningful messages. Use multi-catch to avoid duplicate code. Keep catch (Exception e) last, or omit it if you only expect known types.

5. throw and throws

throw creates an exception at runtime. throws declares checked exceptions a method may propagate to its caller.

  • throw new Exception("message")
  • throws on method signature
  • Caller must handle or rethrow checked
  • Do not throw for normal control flow
throw and throws
import java.io.*;

class FileUtil {
    static void read(String path) throws IOException {
        if (path == null) throw new IllegalArgumentException("null path");
        new FileReader(path);
    }
}

6. Custom Exceptions

Define application-specific exceptions by extending Exception (checked) or RuntimeException (unchecked) with meaningful names and messages.

  • Extend Exception for checked custom errors
  • Extend RuntimeException for unchecked
  • Add constructors with message and cause
  • Document when they are thrown
Custom exception
class InsufficientFundsException extends Exception {
    InsufficientFundsException(String msg) { super(msg); }
}
class Bank {
    void withdraw(double amt) throws InsufficientFundsException {
        throw new InsufficientFundsException("Low balance");
    }
}

7. Exception Handling Best Practices

Handle exceptions at the right level, log useful context, and never swallow errors silently.

  • Catch specific exceptions first
  • Log exception with stack trace
  • Do not use exceptions for flow control
  • Close resources in finally or try-with-resources
  • Include meaningful error messages

8. Practical Exception Examples

Validate input early, wrap low-level exceptions in domain exceptions, and present user-friendly messages at the UI layer.

  • Parse int with NumberFormatException handling
  • Retry logic for transient IO failures
  • Global handler in main for uncaught exceptions
  • Custom validation exceptions
Parse with handling
public class ParseDemo {
    public static void main(String[] args) {
        String input = "abc";
        try {
            int n = Integer.parseInt(input);
            System.out.println(n);
        } catch (NumberFormatException e) {
            System.out.println("Not a number");
        }
    }
}

9. Tricky Points

Common exception-handling pitfalls in interviews and production code — especially around multiple catch blocks.

Multiple catch blocks

Catch order matters: catch (IOException e) must come before catch (Exception e). The reverse order is a compile error because the second catch would never run.
Only one catch executes: If both NumberFormatException and Exception are declared, a bad parse hits the first block only — not both.
Multi-catch restrictions: catch (Exception | IOException e) is invalid because IOException is a subclass of Exception. Both types must be unrelated (or siblings).
Multi-catch variable is final: You cannot write e = new Exception() inside catch (IOException | SQLException e).

finally and return

finally always runs: finally executes even when try or catch contains return, unless System.exit() is called.
return in finally wins: If try { return 1; } and finally { return 2; }, the method returns 2 — the try return is discarded.
Exception in finally masks the original: If try throws and finally also throws, the finally exception is propagated and the original is lost (unless suppressed — see below).

Checked vs unchecked

Checked exceptions: Must be caught or declared with throws. The compiler enforces this for IOException, SQLException, etc.
Unchecked exceptions: RuntimeException and subclasses (e.g. NullPointerException) do not require throws — but you can still catch them.
Error vs Exception: OutOfMemoryError and StackOverflowError extend Error, not Exception. Catching Exception does not catch Error.

Common mistakes

Empty catch block: catch (Exception e) { } swallows errors silently — always log or rethrow with context.
Catching Throwable: Avoid catch (Throwable t) unless you are a top-level framework handler — it catches Error too.
Exceptions for flow control: Do not use throw/catch instead of if for normal logic — it hurts readability and performance.
Suppressed exceptions (Java 7+): In try-with-resources, if both try and close fail, the close exception is added via addSuppressed() on the primary exception — check getSuppressed() when debugging.

Rule of thumb

Catch the most specific type you can handle; order catches from subclass to superclass; use multi-catch for shared logic; never swallow exceptions; prefer try-with-resources over manual finally for I/O.