Skip to content
beginnerPhase 12 · Java Exceptions

Checked vs Unchecked Exceptions

Know the difference and when to use each type.

30m
2 problems
Topic Progress0%

Checked Exceptions

Checked Exceptions

Checked exceptions are exceptions that the compiler forces you to either catch or declare in the method signature using the throws keyword. They represent recoverable conditions that are outside the normal flow of the program but can reasonably be anticipated and handled.

Checked exceptions are subclasses of Exception but NOT subclasses of RuntimeException. The compiler checks for them at compile time, hence the name.

Common checked exceptions:

  • IOException — I/O operations (file not found, network errors)
  • FileNotFoundException — specific file does not exist
  • SQLException — database access errors
  • ClassNotFoundException — class not found at runtime
  • InterruptedException — a thread was interrupted
  • ParseException — parsing error (e.g., date parsing)
import java.io.*;
import java.sql.*;
import java.util.Date;
import java.text.*;

public class CheckedExceptionsDemo {

    // Must declare checked exceptions with throws
    public static String readConfig(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        String config = br.readLine();
        br.close();
        return config;
    }

    // Must catch or declare SQLException
    public static void connectToDatabase() throws SQLException {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/db", "user", "pass");
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM users");
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
    }

    // Must catch ParseException
    public static Date parseDate(String dateStr) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.parse(dateStr);
    }

    // Or catch them locally
    public static void handleLocally() {
        try {
            String config = readConfig("app.properties");
            System.out.println("Config: " + config);
        } catch (IOException e) {
            System.out.println("Using default config");
        }
    }

    // Multiple checked exceptions
    public static void complexOperation() {
        try {
            connectToDatabase();
            Date date = parseDate("2024-01-15");
            System.out.println("Parsed: " + date);
        } catch (SQLException e) {
            System.out.println("Database error: " + e.getMessage());
        } catch (ParseException e) {
            System.out.println("Parse error: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        handleLocally();
        complexOperation();
    }
}

The compiler-enforced handling of checked exceptions is a controversial feature. Supporters argue it makes code more robust by forcing error handling. Critics argue it leads to cluttered code with empty catch blocks. The best practice is to catch checked exceptions only when you can meaningfully recover from them; otherwise, let them propagate.

Unchecked Exceptions

Unchecked Exceptions

Unchecked exceptions are subclasses of RuntimeException. The compiler does not force you to catch or declare them. They typically indicate programming bugs and should be fixed at the source rather than caught generically.

Common unchecked exceptions:

  • NullPointerException — accessing methods/fields on null
  • ArrayIndexOutOfBoundsException — invalid array index
  • StringIndexOutOfBoundsException — invalid string index
  • IllegalArgumentException — invalid method argument
  • IllegalStateException — method called at wrong time
  • ArithmeticException — arithmetic errors (division by zero)
  • NumberFormatException — invalid number format
  • ClassCastException — invalid type cast
  • UnsupportedOperationException — unsupported operation
public class UncheckedExceptionsDemo {

    // NullPointerException
    public static void demonstrateNPE() {
        String str = null;
        // str.length(); // throws NullPointerException
        try {
            str.length();
        } catch (NullPointerException e) {
            System.out.println("NPE: " + e.getMessage());
        }
    }

    // ArrayIndexOutOfBoundsException
    public static void demonstrateAIOOBE() {
        int[] arr = {1, 2, 3};
        try {
            int val = arr[5]; // Index 5 out of bounds
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Invalid index: " + e.getMessage());
        }
    }

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

    // ArithmeticException
    public static void divide(int a, int b) {
        try {
            int result = a / b;
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        }
    }

    // NumberFormatException
    public static void parseNumber(String input) {
        try {
            int num = Integer.parseInt(input);
            System.out.println("Number: " + num);
        } catch (NumberFormatException e) {
            System.out.println("Invalid number: " + input);
        }
    }

    // ClassCastException
    public static void demonstrateCCE() {
        Object obj = "Hello";
        try {
            Integer num = (Integer) obj; // ClassCastException
        } catch (ClassCastException e) {
            System.out.println("Invalid cast: " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        demonstrateNPE();
        demonstrateAIOOBE();
        setAge(25);
        divide(10, 0);
        parseNumber("abc");
        demonstrateCCE();
    }
}

Best practices for unchecked exceptions:

  • Do not catch them generically unless absolutely necessary
  • Use Objects.requireNonNull() for null checks
  • Validate method arguments and throw IllegalArgumentException with descriptive messages
  • Fix the root cause rather than catching the symptom

Errors

Errors

Errors are subclasses of java.lang.Error and represent serious system-level problems that applications should not attempt to catch. They are thrown by the JVM and indicate conditions that are typically unrecoverable.

Common errors:

  • OutOfMemoryError — JVM ran out of memory
  • StackOverflowError — infinite recursion
  • VirtualMachineError — JVM internal error
  • AssertionError — assertion failure
  • NoClassDefFoundError — class definition not found at runtime
  • ThreadDeath — thread killed by JVM
public class ErrorsDemo {

    // StackOverflowError from infinite recursion
    public static void infiniteRecursion() {
        infiniteRecursion(); // calls itself forever
    }

    // OutOfMemoryError from creating too many objects
    public static void outOfMemory() {
        // Allocate memory until JVM runs out
        // List<byte[]> list = new ArrayList<>();
        // while (true) {
        //     list.add(new byte[1024 * 1024]); // 1MB each
        // }
    }

    // Why you should NOT catch Errors
    public static void shouldNotCatch() {
        try {
            infiniteRecursion();
        } catch (StackOverflowError e) {
            // Generally should NOT do this
            // Errors indicate unrecoverable problems
            System.out.println("Stack overflow: " + e.getMessage());
        }
    }

    // Exception vs Error comparison
    public static void demonstrateDifference() {
        // Exception - can recover
        try {
            String str = null;
            str.length(); // RuntimeException
        } catch (NullPointerException e) {
            System.out.println("Recovered from NPE");
        }

        // Error - should not recover
        // try {
        //     infiniteRecursion(); // StackOverflowError
        // } catch (StackOverflowError e) {
        //     // Don't catch errors unless you're sure you can recover
        // }
    }

    public static void main(String[] args) {
        // demonstrateDifference();
        // infiniteRecursion(); // Don't actually run this
        System.out.println("Errors should generally not be caught");
    }
}

When you might catch an Error:

  • In a server application to prevent one request from crashing the entire server
  • In a test framework to verify error conditions
  • When implementing a fallback mechanism for specific known errors

When to NOT catch an Error:

  • In normal application code
  • When the application state is corrupted
  • When you cannot meaningfully recover

The key distinction: Exceptions are for conditions you can recover from. Errors are for conditions that indicate the JVM is in a bad state.

Handling Strategy

Handling Strategy: Catch vs Declare

Choosing whether to catch an exception or let it propagate is one of the most important design decisions in exception handling. The right strategy depends on your role in the call stack and whether you can meaningfully recover.

When to catch:

  • You can recover and continue execution
  • You need to provide fallback behavior
  • You need to clean up resources before propagating
  • You want to add context before rethrowing

When to declare:

  • You cannot recover from the exception
  • The caller is in a better position to handle it
  • You want to let the exception propagate up the call stack
  • The method is a utility that should not make policy decisions
import java.io.*;
import java.util.*;

public class HandlingStrategyDemo {

    // BAD: Swallowing exceptions silently
    public static void badReadFile(String path) {
        try {
            BufferedReader br = new BufferedReader(new FileReader(path));
            System.out.println(br.readLine());
            br.close();
        } catch (IOException e) {
            // Silent failure - BAD practice!
        }
    }

    // GOOD: Catch with meaningful recovery
    public static String goodReadFile(String path) {
        try {
            BufferedReader br = new BufferedReader(new FileReader(path));
            String content = br.readLine();
            br.close();
            return content;
        } catch (IOException e) {
            // Log the error, provide fallback
            System.err.println("Failed to read " + path + ": " + e.getMessage());
            return "default value";
        }
    }

    // GOOD: Declare when you can't recover
    public static List<String> loadAllFiles(List<String> paths) throws IOException {
        List<String> results = new ArrayList<>();
        for (String path : paths) {
            results.add(readFileOrThrow(path)); // Let caller handle
        }
        return results;
    }

    private static String readFileOrThrow(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        String content = br.readLine();
        br.close();
        return content;
    }

    // GOOD: Catch, add context, and rethrow
    public static void processData(String path) {
        try {
            String data = readFileOrThrow(path);
            int value = Integer.parseInt(data.trim());
            System.out.println("Processed: " + (value * 2));
        } catch (IOException e) {
            throw new RuntimeException("Failed to load data from " + path, e);
        } catch (NumberFormatException e) {
            throw new RuntimeException("Invalid data format in " + path, e);
        }
    }

    // GOOD: Catch for cleanup, then rethrow
    public static void processWithCleanup(String path) {
        InputStream is = null;
        try {
            is = new FileInputStream(path);
            // process...
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
            throw new RuntimeException("Processing failed", e);
        } finally {
            if (is != null) {
                try { is.close(); } catch (IOException e) { /* ignore */ }
            }
        }
    }

    // Decision framework
    public static String decideHandling(int errorCode) {
        try {
            return processErrorCode(errorCode);
        } catch (IllegalArgumentException e) {
            // We can recover: return a default
            return "unknown";
        } catch (IOException e) {
            // We cannot recover: propagate
            throw new RuntimeException("Cannot process error code", e);
        }
    }

    private static String processErrorCode(int code) throws IOException {
        if (code < 0) throw new IllegalArgumentException("Negative code");
        if (code > 500) throw new IOException("Server error");
        return "OK";
    }

    public static void main(String[] args) {
        System.out.println(goodReadFile("config.txt"));
        try {
            List<String> files = loadAllFiles(Arrays.asList("a.txt", "b.txt"));
        } catch (IOException e) {
            System.out.println("Could not load files: " + e.getMessage());
        }
    }
}

The golden rule: Catch when you can recover. Declare when you cannot. Never swallow exceptions silently.

Practice Problems

0/2solved
Safe Array Access

Write a method `safeGet(int[] arr, int index)` that returns the element at the given index, or -1 if the index is out of bounds. Handle the unchecked exception.

Solution
public class SafeArrayAccess {
    public static int safeGet(int[] arr, int index) {
        try {
            return arr[index];
        } catch (ArrayIndexOutOfBoundsException e) {
            return -1;
        }
    }
}
Exception Type Identifier

Write a method `identifyType(Object obj)` that throws `NullPointerException` if obj is null, `IllegalArgumentException` if obj is not a String or Integer, and returns a String description of the object's type otherwise.

Solution
public class ExceptionTypeIdentifier {
    public static String identifyType(Object obj) {
        if (obj == null) {
            throw new NullPointerException("Object cannot be null");
        }
        if (obj instanceof String) {
            return "String: " + obj;
        } else if (obj instanceof Integer) {
            return "Integer: " + obj;
        } else {
            throw new IllegalArgumentException("Unsupported type: " + obj.getClass().getSimpleName());
        }
    }
}

Quiz

1. Which of these is a checked exception?

Question 1 options

2. What is the main difference between checked and unchecked exceptions?

Question 2 options

3. When should you catch an Error in Java?

Question 3 options

4. What is the primary purpose of Checked vs Unchecked Exceptions?

Question 4 options

Flashcards

Question

What is a checked exception?

Answer

A checked exception is a subclass of Exception (not RuntimeException) that the compiler forces you to either catch or declare in the method signature using throws. Example: IOException, SQLException.

Question

Name 3 common unchecked exceptions in Java

Answer

NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException. All are subclasses of RuntimeException and do not need to be declared or caught.

Question

When should you catch an exception vs declare it?

Answer

Catch when you can recover from the error and provide fallback behavior. Declare when you cannot recover and the caller is in a better position to handle it. Never swallow exceptions silently.

Question

What is Checked vs Unchecked Exceptions?

Answer

Checked vs Unchecked Exceptions is a key concept in Java programming.

Question

When to use Checked vs Unchecked Exceptions?

Answer

Use Checked vs Unchecked Exceptions when building production systems that require reliability, scalability, and maintainability.

Revision Notes

Key Takeaways

  • 1.Checked exceptions must be caught or declared; unchecked exceptions do not
  • 2.Errors indicate serious JVM problems and should generally not be caught
  • 3.Never swallow exceptions silently — at minimum, log the error
  • 4.Catch when you can recover; declare when you cannot
  • 5.Unchecked exceptions indicate programming bugs that should be fixed

Interview Tips

  • Know the difference between checked and unchecked exceptions and give concrete examples of each
  • Explain why Java has checked exceptions and discuss the debate around them
  • Describe when you would catch an Error vs when you would let it propagate
  • Discuss best practices: never swallow exceptions, catch specific types, add context when rethrowing

Cheat Sheet

Checked vs Unchecked Exceptions

Checked (compile-time enforced)

  • Subclass of Exception (not RuntimeException)
  • Must catch or declare with throws
  • Examples: IOException, SQLException, ParseException
  • Represents recoverable conditions

Unchecked (runtime)

  • Subclass of RuntimeException
  • No compiler enforcement
  • Examples: NullPointerException, IllegalArgumentException, ArithmeticException
  • Represents programming bugs

Errors

  • Subclass of Error
  • JVM/system level problems
  • Examples: OutOfMemoryError, StackOverflowError
  • Generally should NOT catch

Decision Rule

Catch = can recover, add fallback
Declare = cannot recover, let caller handle