Exception Handling

Lesson 0016 — 40 min read

Exception Handling is a mechanism designed to manage runtime errors and maintain normal application control flow. An exception represents an unexpected runtime event (e.g., division by zero, missing file, array index out of bounds) that disrupts execution if left uncaught.

Importance of Exception Handling

1. The Try-Catch / Try-Except Mechanism

The basic construct places risky code inside a try block and handles errors inside one or more catch (or except) blocks.

public class Main {
    public static void main(String[] args) {
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[5]); // ArrayIndexOutOfBoundsException
            int result = 10 / 0;        // ArithmeticException
        }
        catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Error: Array index out of bounds!");
        }
        catch (ArithmeticException e) {
            System.out.println("Error: Division by zero!");
        }

        System.out.println("Program continues...");
    }
}
#include <iostream>
#include <vector>
#include <stdexcept>
using namespace std;

int main() {
    try {
        vector<int> arr = {1, 2, 3};
        cout << arr.at(5) << "\n"; // Throws std::out_of_range
        int result = 10 / 0;
    }
    catch (const out_of_range& e) {
        cout << "Error: Index out of range!\n";
    }
    catch (const exception& e) {
        cout << "Error: " << e.what() << "\n";
    }

    cout << "Program continues...\n";
    return 0;
}
def main():
    try:
        arr = [1, 2, 3]
        print(arr[5])   # IndexError
        result = 10 / 0 # ZeroDivisionError
    except IndexError:
        print("Error: List index out of range!")
    except ZeroDivisionError:
        print("Error: Division by zero!")

    print("Program continues...")

if __name__ == "__main__":
    main()

2. Resource Cleanup: finally vs RAII

When an exception occurs, allocated resources must be freed to avoid leaks.

public class Main {
    public static void main(String[] args) {
        try {
            int result = 10 / 2;
            System.out.println("Result: " + result);
        }
        catch (Exception e) {
            System.out.println("An error occurred.");
        }
        finally {
            // Always executes regardless of exceptions
            System.out.println("Finally block executed (Cleanup).");
        }

        System.out.println("Program continues...");
    }
}
#include <iostream>
#include <stdexcept>
using namespace std;

struct Cleanup {
    ~Cleanup() {
        // RAII destructor runs automatically on stack unwinding
        cout << "Cleanup executed (RAII Destructor).\n";
    }
};

int main() {
    Cleanup c; // Guard object

    try {
        cout << "Inside try block.\n";
        throw runtime_error("Something went wrong!");
    }
    catch (const exception& e) {
        cout << "Caught: " << e.what() << "\n";
    }

    cout << "Program continues...\n";
    return 0;
}
def main():
    try:
        result = 10 / 2
        print("Result:", result)
    except Exception as e:
        print("An error occurred.")
    finally:
        # Always executes regardless of exceptions
        print("Finally block executed (Cleanup).")

    print("Program continues...")

if __name__ == "__main__":
    main()

3. Throwing and Propagating Exceptions

Languages provide keywords to manually trigger exceptions or declare potential exceptions in method signatures.

class Demo {
    // throws declares checked exceptions callers must handle
    static void divide() throws ArithmeticException {
        throw new ArithmeticException("Division by zero!"); // throw keyword
    }

    public static void main(String[] args) {
        try {
            divide();
        }
        catch (ArithmeticException e) {
            System.out.println("Handled: " + e.getMessage());
        }
    }
}
#include <iostream>
#include <stdexcept>
using namespace std;

void divide() {
    // throw keyword raises runtime error
    throw runtime_error("Division by zero!");
}

int main() {
    try {
        divide(); // Exception propagates to main caller
    }
    catch (const exception& e) {
        cout << "Handled: " << e.what() << "\n";
    }
    return 0;
}
def divide():
    # raise keyword triggers exception
    raise ZeroDivisionError("Division by zero!")

def main():
    try:
        divide() # Exception propagates to caller
    except ZeroDivisionError as e:
        print("Handled:", str(e))

if __name__ == "__main__":
    main()

4. Custom Exceptions

Custom exception classes allow developers to model domain-specific error conditions.

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

public class Main {
    public static void main(String[] args) {
        try {
            throw new CustomException("Application error occurred!");
        }
        catch (CustomException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}
#include <iostream>
#include <exception>
using namespace std;

class CustomException : public exception {
public:
    const char* what() const noexcept override {
        return "Application error occurred!";
    }
};

int main() {
    try {
        throw CustomException();
    }
    catch (const CustomException& e) {
        cout << "Caught: " << e.what() << "\n";
    }
    return 0;
}
class CustomException(Exception):
    def __init__(self, message):
        super().__init__(message)

def main():
    try:
        raise CustomException("Application error occurred!")
    except CustomException as e:
        print("Caught:", str(e))

if __name__ == "__main__":
    main()

5. Checked vs Unchecked Exceptions

Java makes a fundamental distinction between Checked Exceptions (enforced by the compiler at build time, e.g. IOException) and Unchecked Exceptions (runtime errors inheriting from RuntimeException).

In contrast, C++ and Python have NO checked exceptions. All exceptions in C++ and Python are unchecked and propagate freely at runtime unless explicitly caught by a caller.

Real-World Example: File I/O Handling

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try {
            File file = new File("nonexistent.txt");
            Scanner reader = new Scanner(file); // Throws FileNotFoundException
        }
        catch (FileNotFoundException e) {
            System.out.println("Error: File not found!");
        }
    }
}
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    try {
        ifstream file("nonexistent.txt");
        file.exceptions(ifstream::failbit | ifstream::badbit);
        string line;
        getline(file, line);
    }
    catch (const ios_base::failure& e) {
        cout << "Error: File not found or unreadable!\n";
    }
    return 0;
}
def main():
    try:
        with open("nonexistent.txt", "r") as f:
            data = f.read()
    except FileNotFoundError:
        print("Error: File not found!")

if __name__ == "__main__":
    main()

Comparison Summary

Feature Java C++ Python
Trigger Keyword throw new Exception() throw Exception() raise Exception()
Signature Declaration throws ExceptionType No throws; use noexcept for non-throwing No declaration required (docstrings used)
Resource Cleanup finally block / try-with-resources RAII (destructors auto-run on stack unwind) finally block / with context managers
Checked Exceptions Yes (e.g. IOException) No (all exceptions are unchecked) No (all exceptions are unchecked)

How does C++ achieve automatic resource cleanup during exceptions without a finally block?

Which language feature requires callers to catch or declare exceptions at compile time?

Notes

Exception Handling :-

Definition ⇒ Intercept runtime errors to prevent program crashes and preserve control flow

Flow ⇒ try (risky code) — catch/except (handler) — finally/RAII (cleanup)

Resource Cleanup :-

Java / Python ⇒ finally block executes regardless of exceptions ; Python uses with context managers

C++ ⇒ No finally ; uses RAII (destructors auto-called during stack unwinding)

Throwing & Declarations :-

Throwing ⇒ Java/C++ throw ; Python raise

Signatures ⇒ Java throws forces caller handling ; C++ uses optional noexcept ; Python relies on propagation

Catching Rule ⇒ Catch by const reference in C++ to prevent object slicing

Checked vs Unchecked :-

Java ⇒ Checked (compile-time enforced, eg IOException) vs Unchecked (RuntimeException)

C++ & Python ⇒ No checked exceptions ; all exceptions are unchecked

Primary source: Oracle Java Tutorials — Exceptions, cppreference — Exceptions & Python Docs — Errors and Exceptions. Ask me anything that's unclear.

← Prev: Object Cloning Next: Generics & Templates →

Questions? Ask your agent — you can follow up on any concept, quiz answer, or how this applies to a specific coding problem. Review the Java glossary or syntax quick reference for a compressed overview.