Home/Learn/Java A–Z/Exception Handling

Exception Handling

Beginner
OOP & Advanced Classes

Handle errors gracefully with try/catch/finally, distinguish checked from unchecked exceptions, and write custom exception classes.

Overview

Exceptions are Java's mechanism for signalling and handling error conditions. The exception hierarchy starts at Throwable, splits into Error (JVM-level, don't catch) and Exception (application-level). Checked exceptions must be declared or handled at compile time; unchecked exceptions (RuntimeException and subclasses) do not. Java 7 introduced multi-catch and try-with-resources — both reduce boilerplate significantly. Good exception design means throwing specific exceptions, never swallowing them silently, and always cleaning up resources.

try / catch / finally & Exception Hierarchy

The exception hierarchy: Throwable ├── Error (OutOfMemoryError, StackOverflowError) — never catch these └── Exception ├── Checked exceptions (IOException, SQLException) — must handle or declare └── RuntimeException (unchecked) ├── NullPointerException ├── ArrayIndexOutOfBoundsException ├── IllegalArgumentException └── ...

finally always runs — even if an exception is thrown or a return statement executes. Use it to release resources. Note: if both the try block and the finally block throw, the try block's exception is lost.

ExceptionDemo.java
import java.io.IOException;

public class ExceptionDemo {

    // Checked exception — must be caught or declared with throws
    static String readFile(String path) throws IOException {
        if (path == null) throw new IOException("Path cannot be null");
        return "file content";
    }

    public static void main(String[] args) {
        // Basic try-catch-finally
        try {
            String content = readFile(null);
            System.out.println(content);
        } catch (IOException e) {
            System.out.println("IO error: " + e.getMessage()); // IO error: Path cannot be null
        } finally {
            System.out.println("finally always runs"); // always executes
        }

        // Multi-catch (Java 7+) — handle multiple types in one block
        try {
            String s = null;
            int[] arr = new int[3];
            s.length();        // NullPointerException
            arr[5] = 1;        // ArrayIndexOutOfBoundsException
        } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
            System.out.println("Caught: " + e.getClass().getSimpleName());
        }

        // Exception chaining — wrap root cause
        try {
            try {
                int result = 10 / 0;
            } catch (ArithmeticException e) {
                throw new RuntimeException("Calculation failed", e); // wraps original
            }
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());           // Calculation failed
            System.out.println(e.getCause().getMessage()); // / by zero
        }
    }
}

try-with-resources & Custom Exceptions

try-with-resources (Java 7+) automatically closes any AutoCloseable resource at the end of the block — even if an exception occurs. This replaces verbose null-check + finally close patterns. Multiple resources are allowed, closed in reverse order of declaration.

Custom exception classes: extend RuntimeException for unchecked (callers aren't forced to handle), or extend Exception for checked. Always provide constructors that accept a message and a cause (for exception chaining).

ResourceDemo.java
import java.io.*;

// Custom exceptions
public class InsufficientFundsException extends RuntimeException {
    private final double shortage;

    public InsufficientFundsException(double shortage) {
        super("Insufficient funds. Short by: $" + shortage);
        this.shortage = shortage;
    }

    public InsufficientFundsException(double shortage, Throwable cause) {
        super("Insufficient funds. Short by: $" + shortage, cause);
        this.shortage = shortage;
    }

    public double getShortage() { return shortage; }
}

public class ResourceDemo {

    // Simulated AutoCloseable resource
    static class DbConnection implements AutoCloseable {
        public DbConnection(String url) { System.out.println("Opened: " + url); }
        public String query(String sql) { return "result"; }
        @Override public void close()   { System.out.println("Connection closed"); }
    }

    public static void main(String[] args) {
        // try-with-resources — close() called automatically
        try (DbConnection conn = new DbConnection("jdbc:localhost/db")) {
            String result = conn.query("SELECT 1");
            System.out.println(result);
        } // close() runs here, even if exception was thrown

        // Multiple resources — closed in REVERSE declaration order
        try (var in  = new BufferedReader(new StringReader("line1
line2"));
             var out = new StringWriter()) {
            String line;
            while ((line = in.readLine()) != null) out.write(line + " ");
            System.out.println(out.toString().trim()); // line1 line2
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Custom exception
        try {
            double balance = 100, withdrawal = 150;
            if (withdrawal > balance)
                throw new InsufficientFundsException(withdrawal - balance);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());    // Insufficient funds. Short by: $50.0
            System.out.println(e.getShortage());   // 50.0
        }
    }
}

Exception Handling Best Practices

Common mistakes and how to fix them:

Never swallow exceptions — empty catch blocks hide bugs. At minimum, log the exception. Catch specific types — catch (Exception e) hides NullPointerException, bugs, etc. Catch the narrowest applicable type. Don't use exceptions for flow control — exceptions are expensive (stack trace capture). Use if/else for expected conditions. Always wrap causes — when rethrowing, include the original exception as the cause so the root cause is not lost. Clean up resources — use try-with-resources, not finally blocks.

BestPractices.java
import java.util.logging.Logger;

public class BestPractices {
    private static final Logger log = Logger.getLogger(BestPractices.class.getName());

    // BAD: swallowing exception
    static int parseBad(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            // silent — caller never knows it failed
            return 0;
        }
    }

    // GOOD: translate to domain exception with cause
    static int parseGood(String s) {
        try {
            return Integer.parseInt(s);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid number: '" + s + "'", e);
        }
    }

    // GOOD: use Optional for "not found" — not an exception
    static java.util.Optional<Integer> tryParse(String s) {
        try {
            return java.util.Optional.of(Integer.parseInt(s));
        } catch (NumberFormatException e) {
            return java.util.Optional.empty();
        }
    }

    public static void main(String[] args) {
        System.out.println(parseBad("abc"));   // 0 — no indication of error!

        try {
            parseGood("abc");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());        // Invalid number: 'abc'
            System.out.println(e.getCause().getClass().getSimpleName()); // NumberFormatException
        }

        tryParse("42").ifPresent(n -> System.out.println("Parsed: " + n));
        tryParse("x").ifPresentOrElse(n -> {}, () -> System.out.println("Not a number"));
    }
}

Key Points to Remember

  • Checked exceptions (extend Exception) must be caught or declared; unchecked (extend RuntimeException) do not
  • Never catch Error — JVM errors like OutOfMemoryError are unrecoverable
  • finally always runs; try-with-resources (Java 7+) is the modern way to close AutoCloseable resources
  • Always chain exceptions with cause: throw new MyException("msg", originalException)
  • Never swallow exceptions with an empty catch block — at minimum log them
  • Catch the narrowest applicable exception type; avoid catching bare Exception or Throwable

Practice Exception Handling in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is the difference between checked and unchecked exceptions?

EasyTCS
2

What is try-with-resources and what interface must a resource implement?

MediumAmazon
3

Does finally always run? Give a case where it does not.

MediumGoogle
4

How do you create a custom exception class?

EasyInfosys
5

What is exception chaining and why is it important?

MediumMicrosoft

Ask Aria about Exception Handling

Your personal AI tutor — ask anything about this concept