Your validation method checks if age is negative. You need to signal "this is wrong" to the caller. throw creates and throws an exception. throws in the signature warns callers what might go wrong.

Throw an exception

Manually trigger an exception.

ThrowKeyword.java
// The throw Keyword

public class ThrowKeyword {
    public static void main(String[] args) {
        System.out.println("=== The throw Keyword ===\n");

        // Basic throw
        System.out.println("--- Basic throw ---");

        int testAge = ;
        try {
            validateAge(testAge);
        } catch (IllegalArgumentException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        try {
            validateAge(25);
            System.out.println("Age 25 is valid!");
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }

        // Throw different exception types
        System.out.println("\n--- Different Exception Types ---");

        testExceptionTypes();

        // Throw with condition
        System.out.println("\n--- Conditional throw ---");

        String[] names = {"Alice", "", "Bob", null};

        for (String name : names) {
            try {
                validateName(name);
                System.out.println("'" + name + "' is valid");
            } catch (IllegalArgumentException e) {
                System.out.println("Invalid name: " + e.getMessage());
            }
        }

        // Throw in constructor
        System.out.println("\n--- Throw in Constructor ---");

        try {
            Person p1 = new Person("John", 30);
            System.out.println("Created: " + p1);

            Person p2 = new Person("", 25);
            System.out.println("Created: " + p2);
        } catch (IllegalArgumentException e) {
            System.out.println("Cannot create Person: " + e.getMessage());
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. throw new ExceptionType("message")
            2. throw immediately exits the method
            3. Can throw from anywhere: methods, constructors, blocks
            4. Must throw a Throwable (usually Exception subclass)
            5. Code after throw in same block won't execute
            """);
    }

    static void validateAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative: " + age);
        }
        if (age > 150) {
            throw new IllegalArgumentException("Age unrealistic: " + age);
        }
        // If we reach here, age is valid
        System.out.println("Validating age: " + age + " - OK");
    }

    static void validateName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name cannot be null");
        }
        if (name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
    }

    static void testExceptionTypes() {
        // Different exception types

        String[] tests = {"null_pointer", "illegal_arg", "illegal_state", "arithmetic"};

        for (String test : tests) {
            try {
                throwSpecificType(test);
            } catch (RuntimeException e) {
                System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
            }
        }
    }

    static void throwSpecificType(String type) {
        switch (type) {
            case "null_pointer" -> throw new NullPointerException("Something was null");
            case "illegal_arg" -> throw new IllegalArgumentException("Bad argument");
            case "illegal_state" -> throw new IllegalStateException("Wrong state");
            case "arithmetic" -> throw new ArithmeticException("Math error");
        }
    }
}

class Person {
    String name;
    int age;

    Person(String name, int age) {
        // Validate in constructor
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name is required");
        }
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throw Keyword

public class ThrowKeyword {
    public static void main(String[] args) {
        System.out.println("=== The throw Keyword ===\n");

        // Basic throw
        System.out.println("--- Basic throw ---");

        int testAge = ;
        try {
            validateAge(testAge);
        } catch (IllegalArgumentException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        try {
            validateAge(25);
            System.out.println("Age 25 is valid!");
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }

        // Throw different exception types
        System.out.println("\n--- Different Exception Types ---");

        testExceptionTypes();

        // Throw with condition
        System.out.println("\n--- Conditional throw ---");

        String[] names = {"Alice", "", "Bob", null};

        for (String name : names) {
            try {
                validateName(name);
                System.out.println("'" + name + "' is valid");
            } catch (IllegalArgumentException e) {
                System.out.println("Invalid name: " + e.getMessage());
            }
        }

        // Throw in constructor
        System.out.println("\n--- Throw in Constructor ---");

        try {
            Person p1 = new Person("John", 30);
            System.out.println("Created: " + p1);

            Person p2 = new Person("", 25);
            System.out.println("Created: " + p2);
        } catch (IllegalArgumentException e) {
            System.out.println("Cannot create Person: " + e.getMessage());
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. throw new ExceptionType("message")
            2. throw immediately exits the method
            3. Can throw from anywhere: methods, constructors, blocks
            4. Must throw a Throwable (usually Exception subclass)
            5. Code after throw in same block won't execute
            """);
    }

    static void validateAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative: " + age);
        }
        if (age > 150) {
            throw new IllegalArgumentException("Age unrealistic: " + age);
        }
        // If we reach here, age is valid
        System.out.println("Validating age: " + age + " - OK");
    }

    static void validateName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name cannot be null");
        }
        if (name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
    }

    static void testExceptionTypes() {
        // Different exception types

        String[] tests = {"null_pointer", "illegal_arg", "illegal_state", "arithmetic"};

        for (String test : tests) {
            try {
                throwSpecificType(test);
            } catch (RuntimeException e) {
                System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
            }
        }
    }

    static void throwSpecificType(String type) {
        switch (type) {
            case "null_pointer" -> throw new NullPointerException("Something was null");
            case "illegal_arg" -> throw new IllegalArgumentException("Bad argument");
            case "illegal_state" -> throw new IllegalStateException("Wrong state");
            case "arithmetic" -> throw new ArithmeticException("Math error");
        }
    }
}

class Person {
    String name;
    int age;

    Person(String name, int age) {
        // Validate in constructor
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name is required");
        }
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throw Keyword

public class ThrowKeyword {
    public static void main(String[] args) {
        System.out.println("=== The throw Keyword ===\n");

        // Basic throw
        System.out.println("--- Basic throw ---");

        int testAge = ;
        try {
            validateAge(testAge);
        } catch (IllegalArgumentException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        try {
            validateAge(25);
            System.out.println("Age 25 is valid!");
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }

        // Throw different exception types
        System.out.println("\n--- Different Exception Types ---");

        testExceptionTypes();

        // Throw with condition
        System.out.println("\n--- Conditional throw ---");

        String[] names = {"Alice", "", "Bob", null};

        for (String name : names) {
            try {
                validateName(name);
                System.out.println("'" + name + "' is valid");
            } catch (IllegalArgumentException e) {
                System.out.println("Invalid name: " + e.getMessage());
            }
        }

        // Throw in constructor
        System.out.println("\n--- Throw in Constructor ---");

        try {
            Person p1 = new Person("John", 30);
            System.out.println("Created: " + p1);

            Person p2 = new Person("", 25);
            System.out.println("Created: " + p2);
        } catch (IllegalArgumentException e) {
            System.out.println("Cannot create Person: " + e.getMessage());
        }

        System.out.println("\n=== Key Points ===");
        System.out.println("""
            1. throw new ExceptionType("message")
            2. throw immediately exits the method
            3. Can throw from anywhere: methods, constructors, blocks
            4. Must throw a Throwable (usually Exception subclass)
            5. Code after throw in same block won't execute
            """);
    }

    static void validateAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative: " + age);
        }
        if (age > 150) {
            throw new IllegalArgumentException("Age unrealistic: " + age);
        }
        // If we reach here, age is valid
        System.out.println("Validating age: " + age + " - OK");
    }

    static void validateName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name cannot be null");
        }
        if (name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }
    }

    static void testExceptionTypes() {
        // Different exception types

        String[] tests = {"null_pointer", "illegal_arg", "illegal_state", "arithmetic"};

        for (String test : tests) {
            try {
                throwSpecificType(test);
            } catch (RuntimeException e) {
                System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
            }
        }
    }

    static void throwSpecificType(String type) {
        switch (type) {
            case "null_pointer" -> throw new NullPointerException("Something was null");
            case "illegal_arg" -> throw new IllegalArgumentException("Bad argument");
            case "illegal_state" -> throw new IllegalStateException("Wrong state");
            case "arithmetic" -> throw new ArithmeticException("Math error");
        }
    }
}

class Person {
    String name;
    int age;

    Person(String name, int age) {
        // Validate in constructor
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name is required");
        }
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

throw new Exception("message") creates and throws immediately.

throw Trigger an exception: `throw new IllegalArgumentException("Invalid")`.

Declare with throws

Tell callers what exceptions a method might throw.

ThrowsDeclaration.java
// The throws Declaration

import java.io.IOException;
import java.text.ParseException;

public class ThrowsDeclaration {
    public static void main(String[] args) {
        System.out.println("=== The throws Declaration ===\n");

        // Method that throws unchecked exception
        System.out.println("--- Unchecked Exceptions (no throws needed) ---");

        try {
            divideNumbers(10, 0);
        } catch (ArithmeticException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Method that throws checked exception
        System.out.println("\n--- Checked Exceptions (throws required) ---");

        try {
            processFile("data.txt");
        } catch (IOException e) {
            System.out.println("Caught IOException: " + e.getMessage());
        }

        // Multiple throws declaration
        System.out.println("\n--- Multiple Exceptions in throws ---");

        String data = ;
        try {
            parseAndProcess(data);
        } catch (IOException e) {
            System.out.println("IO Error: " + e.getMessage());
        } catch (ParseException e) {
            System.out.println("Parse Error: " + e.getMessage());
        }

        // Throws vs handling
        System.out.println("\n--- Throws vs Handling ---");

        demonstrateChoice();

        // Throws in main method
        System.out.println("\n=== throws in main() ===");
        System.out.println("""
            // main can also declare throws:
            public static void main(String[] args) throws IOException {
                // If IOException occurs, program crashes
                // No try-catch needed
            }

            Note: Usually better to catch in main rather than throws.
            """);

        System.out.println("=== Key Points ===");
        System.out.println("""
            1. throws goes in method signature after parameters
            2. Required for checked exceptions (not RuntimeException)
            3. Multiple: throws IOException, ParseException
            4. Caller must catch OR also declare throws
            5. Unchecked exceptions don't require throws
            """);
    }

    // Unchecked - no throws needed
    static int divideNumbers(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return a / b;
    }

    // Checked - throws required
    static void processFile(String filename) throws IOException {
        System.out.println("Attempting to process: " + filename);

        // Simulating file operation that might fail
        if (!filename.endsWith(".txt")) {
            throw new IOException("Only .txt files supported");
        }

        // Simulate file not found
        if (filename.equals("data.txt")) {
            throw new IOException("File not found: " + filename);
        }

        System.out.println("File processed successfully");
    }

    // Multiple exceptions in throws
    static void parseAndProcess(String data) throws IOException, ParseException {
        System.out.println("Processing data: " + data);

        if (data == null || data.isEmpty()) {
            throw new IOException("No data provided");
        }

        if (!data.matches("\\d+")) {
            throw new ParseException("Data is not numeric: " + data, 0);
        }

        System.out.println("Data valid: " + data);
    }

    static void demonstrateChoice() {
        System.out.println("Option 1: Catch the exception");
        System.out.println("  try { processFile(); } catch (IOException e) { ... }");
        System.out.println();
        System.out.println("Option 2: Propagate with throws");
        System.out.println("  void myMethod() throws IOException { processFile(); }");
        System.out.println();
        System.out.println("Choose based on whether YOU can handle the error,");
        System.out.println("or if the CALLER should handle it.");
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throws Declaration

import java.io.IOException;
import java.text.ParseException;

public class ThrowsDeclaration {
    public static void main(String[] args) {
        System.out.println("=== The throws Declaration ===\n");

        // Method that throws unchecked exception
        System.out.println("--- Unchecked Exceptions (no throws needed) ---");

        try {
            divideNumbers(10, 0);
        } catch (ArithmeticException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Method that throws checked exception
        System.out.println("\n--- Checked Exceptions (throws required) ---");

        try {
            processFile("data.txt");
        } catch (IOException e) {
            System.out.println("Caught IOException: " + e.getMessage());
        }

        // Multiple throws declaration
        System.out.println("\n--- Multiple Exceptions in throws ---");

        String data = ;
        try {
            parseAndProcess(data);
        } catch (IOException e) {
            System.out.println("IO Error: " + e.getMessage());
        } catch (ParseException e) {
            System.out.println("Parse Error: " + e.getMessage());
        }

        // Throws vs handling
        System.out.println("\n--- Throws vs Handling ---");

        demonstrateChoice();

        // Throws in main method
        System.out.println("\n=== throws in main() ===");
        System.out.println("""
            // main can also declare throws:
            public static void main(String[] args) throws IOException {
                // If IOException occurs, program crashes
                // No try-catch needed
            }

            Note: Usually better to catch in main rather than throws.
            """);

        System.out.println("=== Key Points ===");
        System.out.println("""
            1. throws goes in method signature after parameters
            2. Required for checked exceptions (not RuntimeException)
            3. Multiple: throws IOException, ParseException
            4. Caller must catch OR also declare throws
            5. Unchecked exceptions don't require throws
            """);
    }

    // Unchecked - no throws needed
    static int divideNumbers(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return a / b;
    }

    // Checked - throws required
    static void processFile(String filename) throws IOException {
        System.out.println("Attempting to process: " + filename);

        // Simulating file operation that might fail
        if (!filename.endsWith(".txt")) {
            throw new IOException("Only .txt files supported");
        }

        // Simulate file not found
        if (filename.equals("data.txt")) {
            throw new IOException("File not found: " + filename);
        }

        System.out.println("File processed successfully");
    }

    // Multiple exceptions in throws
    static void parseAndProcess(String data) throws IOException, ParseException {
        System.out.println("Processing data: " + data);

        if (data == null || data.isEmpty()) {
            throw new IOException("No data provided");
        }

        if (!data.matches("\\d+")) {
            throw new ParseException("Data is not numeric: " + data, 0);
        }

        System.out.println("Data valid: " + data);
    }

    static void demonstrateChoice() {
        System.out.println("Option 1: Catch the exception");
        System.out.println("  try { processFile(); } catch (IOException e) { ... }");
        System.out.println();
        System.out.println("Option 2: Propagate with throws");
        System.out.println("  void myMethod() throws IOException { processFile(); }");
        System.out.println();
        System.out.println("Choose based on whether YOU can handle the error,");
        System.out.println("or if the CALLER should handle it.");
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
// The throws Declaration

import java.io.IOException;
import java.text.ParseException;

public class ThrowsDeclaration {
    public static void main(String[] args) {
        System.out.println("=== The throws Declaration ===\n");

        // Method that throws unchecked exception
        System.out.println("--- Unchecked Exceptions (no throws needed) ---");

        try {
            divideNumbers(10, 0);
        } catch (ArithmeticException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Method that throws checked exception
        System.out.println("\n--- Checked Exceptions (throws required) ---");

        try {
            processFile("data.txt");
        } catch (IOException e) {
            System.out.println("Caught IOException: " + e.getMessage());
        }

        // Multiple throws declaration
        System.out.println("\n--- Multiple Exceptions in throws ---");

        String data = ;
        try {
            parseAndProcess(data);
        } catch (IOException e) {
            System.out.println("IO Error: " + e.getMessage());
        } catch (ParseException e) {
            System.out.println("Parse Error: " + e.getMessage());
        }

        // Throws vs handling
        System.out.println("\n--- Throws vs Handling ---");

        demonstrateChoice();

        // Throws in main method
        System.out.println("\n=== throws in main() ===");
        System.out.println("""
            // main can also declare throws:
            public static void main(String[] args) throws IOException {
                // If IOException occurs, program crashes
                // No try-catch needed
            }

            Note: Usually better to catch in main rather than throws.
            """);

        System.out.println("=== Key Points ===");
        System.out.println("""
            1. throws goes in method signature after parameters
            2. Required for checked exceptions (not RuntimeException)
            3. Multiple: throws IOException, ParseException
            4. Caller must catch OR also declare throws
            5. Unchecked exceptions don't require throws
            """);
    }

    // Unchecked - no throws needed
    static int divideNumbers(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return a / b;
    }

    // Checked - throws required
    static void processFile(String filename) throws IOException {
        System.out.println("Attempting to process: " + filename);

        // Simulating file operation that might fail
        if (!filename.endsWith(".txt")) {
            throw new IOException("Only .txt files supported");
        }

        // Simulate file not found
        if (filename.equals("data.txt")) {
            throw new IOException("File not found: " + filename);
        }

        System.out.println("File processed successfully");
    }

    // Multiple exceptions in throws
    static void parseAndProcess(String data) throws IOException, ParseException {
        System.out.println("Processing data: " + data);

        if (data == null || data.isEmpty()) {
            throw new IOException("No data provided");
        }

        if (!data.matches("\\d+")) {
            throw new ParseException("Data is not numeric: " + data, 0);
        }

        System.out.println("Data valid: " + data);
    }

    static void demonstrateChoice() {
        System.out.println("Option 1: Catch the exception");
        System.out.println("  try { processFile(); } catch (IOException e) { ... }");
        System.out.println();
        System.out.println("Option 2: Propagate with throws");
        System.out.println("  void myMethod() throws IOException { processFile(); }");
        System.out.println();
        System.out.println("Choose based on whether YOU can handle the error,");
        System.out.println("or if the CALLER should handle it.");
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

void method() throws IOException - caller must handle or declare.

throws Method declaration: `void read() throws IOException`. Compiler enforces handling.

Exception propagation

Exceptions bubble up until caught.

Propagation.java
// Exception Propagation

public class Propagation {
    public static void main(String[] args) {
        System.out.println("=== Exception Propagation ===\n");

        // Show the call stack
        System.out.println("Call stack: main() → level1() → level2() → level3()");
        System.out.println();

        // Exception bubbles up
        System.out.println("--- Exception Bubbles Up ---");

        try {
            System.out.println("main: Calling level1()...");
            level1();
            System.out.println("main: level1() returned");
        } catch (RuntimeException e) {
            System.out.println("main: Caught exception!");
            System.out.println("main: Message = " + e.getMessage());
        }

        System.out.println("main: Continuing after catch...\n");

        // Where to catch?
        System.out.println("--- Where Should You Catch? ---");
        demonstrateCatchLevels();

        // Propagation visualization
        System.out.println("\n--- Propagation Visualization ---");
        System.out.println("""
            Call stack (going down):
            ┌─────────────────────┐
            │  main()             │  ← catches here
            │    ↓                │
            │  level1()           │  ← no catch, propagates up
            │    ↓                │
            │  level2()           │  ← no catch, propagates up
            │    ↓                │
            │  level3()           │  ← THROWS exception
            └─────────────────────┘

            Exception propagates (going up):
            level3() → level2() → level1() → main() [CAUGHT]
            """);

        System.out.println("=== Key Points ===");
        System.out.println("""
            1. Exception "bubbles up" the call stack
            2. Each method can catch OR let it propagate
            3. Catch where you can meaningfully handle
            4. If never caught, program crashes
            5. Stack trace shows propagation path
            """);
    }

    static void level1() {
        System.out.println("  level1: Calling level2()...");
        level2();
        System.out.println("  level1: level2() returned");
    }

    static void level2() {
        System.out.println("    level2: Calling level3()...");
        level3();
        System.out.println("    level2: level3() returned");
    }

    static void level3() {
        System.out.println("      level3: About to throw...");
        throw new RuntimeException("Error in level3!");
        // Code below never executes
    }

    static void demonstrateCatchLevels() {
        System.out.println("\nOption 1: Catch at the source (level3)");
        catchAtLevel3();

        System.out.println("\nOption 2: Catch in the middle (level2)");
        catchAtLevel2();

        System.out.println("\nOption 3: Catch at the top (level1)");
        catchAtLevel1();
    }

    static void catchAtLevel3() {
        System.out.println("  Handling error immediately where it occurs");
        try {
            throw new RuntimeException("Error!");
        } catch (RuntimeException e) {
            System.out.println("  Caught in same method: " + e.getMessage());
        }
    }

    static void catchAtLevel2() {
        try {
            doRiskyOperation();
        } catch (RuntimeException e) {
            System.out.println("  Caught one level up: " + e.getMessage());
        }
    }

    static void doRiskyOperation() {
        throw new RuntimeException("Error in risky operation!");
    }

    static void catchAtLevel1() {
        try {
            intermediate();
        } catch (RuntimeException e) {
            System.out.println("  Caught two levels up: " + e.getMessage());
        }
    }

    static void intermediate() {
        deepMethod();
    }

    static void deepMethod() {
        throw new RuntimeException("Error from deep!");
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

Uncaught exception travels up the call stack to the first handler.

Checked vs unchecked

Some exceptions must be handled, others don't.

CheckedVsUnchecked.java
// Checked vs Unchecked Exceptions

import java.io.IOException;

public class CheckedVsUnchecked {
    public static void main(String[] args) {
        System.out.println("=== Checked vs Unchecked Exceptions ===\n");

        // Exception hierarchy
        System.out.println("--- Exception Hierarchy ---");
        System.out.println("""
            Throwable
            ├── Error (don't catch - system errors)
            │   └── OutOfMemoryError, StackOverflowError
            └── Exception
                ├── RuntimeException (UNCHECKED)
                │   ├── NullPointerException
                │   ├── IllegalArgumentException
                │   ├── ArithmeticException
                │   └── IndexOutOfBoundsException
                └── Other Exceptions (CHECKED)
                    ├── IOException
                    ├── SQLException
                    └── ParseException
            """);

        // Unchecked exceptions
        System.out.println("--- Unchecked Exceptions (RuntimeException) ---");
        System.out.println("• No 'throws' required");
        System.out.println("• try-catch is optional");
        System.out.println("• Usually programming errors\n");

        testUnchecked();

        // Checked exceptions
        System.out.println("\n--- Checked Exceptions ---");
        System.out.println("• 'throws' declaration required");
        System.out.println("• Caller MUST catch or declare throws");
        System.out.println("• Usually external failures (IO, network)\n");

        testChecked();

        // Comparison
        System.out.println("\n--- Side-by-Side Comparison ---");

        showComparison();

        System.out.println("\n=== When to Use Which? ===");
        System.out.println("""
            UNCHECKED (RuntimeException):
            • Programming errors (bugs)
            • Invalid arguments
            • Null pointer issues
            • Logic errors

            CHECKED (Exception):
            • External failures
            • File not found
            • Network errors
            • Database connection issues
            • Things caller should be prepared to handle
            """);
    }

    // No throws needed for unchecked
    static void testUnchecked() {
        System.out.println("Method signature: static void testUnchecked()");
        System.out.println("No 'throws' needed!\n");

        // Method that throws unchecked - no declaration
        System.out.println("Calling methods that throw unchecked exceptions:");

        try {
            throwNullPointer();
        } catch (NullPointerException e) {
            System.out.println("  Caught NullPointerException: " + e.getMessage());
        }

        try {
            throwIllegalArg();
        } catch (IllegalArgumentException e) {
            System.out.println("  Caught IllegalArgumentException: " + e.getMessage());
        }
    }

    // No throws in signature
    static void throwNullPointer() {
        throw new NullPointerException("Value was null");
    }

    static void throwIllegalArg() {
        throw new IllegalArgumentException("Bad argument");
    }

    // Throws required for checked
    static void testChecked() {
        System.out.println("Caller must use try-catch OR declare throws\n");

        // Option 1: Use try-catch
        System.out.println("Option 1: try-catch (handle here)");
        try {
            readFile("test.txt");
        } catch (IOException e) {
            System.out.println("  Caught IOException: " + e.getMessage());
        }

        // Option 2 would be: declare throws in method signature
        System.out.println("\nOption 2: Declare 'throws' (propagate to caller)");
        System.out.println("  void myMethod() throws IOException { readFile(); }");
    }

    // throws IOException is REQUIRED
    static void readFile(String filename) throws IOException {
        throw new IOException("Cannot read file: " + filename);
    }

    static void showComparison() {
        System.out.println("""
            ┌────────────────────────────────────────────────────────────┐
            │ Feature              │ Unchecked        │ Checked          │
            ├────────────────────────────────────────────────────────────┤
            │ Base class           │ RuntimeException │ Exception        │
            │ 'throws' required    │ No               │ Yes              │
            │ Catch required       │ No               │ Yes              │
            │ Compiler checks      │ No               │ Yes              │
            │ Typical cause        │ Programming bug  │ External failure │
            │ Example              │ NullPointerEx    │ IOException      │
            └────────────────────────────────────────────────────────────┘
            """);

        // Code comparison
        System.out.println("Code Comparison:");
        System.out.println();
        System.out.println("// UNCHECKED - compiles fine without try-catch:");
        System.out.println("void demo1() {");
        System.out.println("    throw new IllegalArgumentException(\"error\");");
        System.out.println("}");
        System.out.println();
        System.out.println("// CHECKED - compiler error without throws/try-catch:");
        System.out.println("void demo2() throws IOException {  // Must declare!");
        System.out.println("    throw new IOException(\"error\");");
        System.out.println("}");
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

Checked: must handle or declare (IOException). Unchecked: optional (NullPointerException).

checked exception Compiler requires handling: `IOException`, `SQLException`. Recoverable errors.

Catch, log, rethrow

Handle partially then pass along.

Rethrow.java
// Catching, Processing, and Rethrowing

public class Rethrow {
    public static void main(String[] args) {
        System.out.println("=== Catching and Rethrowing ===\n");

        // Simple rethrow
        System.out.println("--- Simple Rethrow ---");

        try {
            simpleRethrow();
        } catch (RuntimeException e) {
            System.out.println("Final catch: " + e.getMessage());
        }

        // Rethrow with additional context
        System.out.println("\n--- Rethrow with Additional Context ---");

        try {
            rethrowWithContext("user123");
        } catch (RuntimeException e) {
            System.out.println("Final catch: " + e.getMessage());
            System.out.println("Cause: " + e.getCause());
        }

        // Rethrow as different type
        System.out.println("\n--- Rethrow as Different Type ---");

        try {
            wrapAndRethrow();
        } catch (RuntimeException e) {
            System.out.println("Exception type: " + e.getClass().getSimpleName());
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                System.out.println("Original cause: " + e.getCause().getClass().getSimpleName());
            }
        }

        // Log and rethrow
        System.out.println("\n--- Log and Rethrow ---");

        try {
            logAndRethrow();
        } catch (RuntimeException e) {
            System.out.println("Handled at top level");
        }

        // Conditional rethrow
        System.out.println("\n--- Conditional Rethrow ---");

        conditionalRethrowDemo();

        System.out.println("\n=== Common Patterns ===");
        System.out.println("""
            1. Log and rethrow (don't swallow)
            2. Wrap in higher-level exception
            3. Add context information
            4. Convert checked to unchecked
            5. Conditional handling
            """);
    }

    static void simpleRethrow() {
        try {
            throw new RuntimeException("Original error");
        } catch (RuntimeException e) {
            System.out.println("Caught, now rethrowing...");
            throw e;
        }
    }

    static void rethrowWithContext(String userId) {
        try {
            processUser(userId);
        } catch (RuntimeException e) {
            // Add context and rethrow
            throw new RuntimeException(
                "Failed to process user: " + userId,
                e  // Original exception as cause
            );
        }
    }

    static void processUser(String userId) {
        throw new RuntimeException("Database connection failed");
    }

    static void wrapAndRethrow() {
        try {
            riskyOperation();
        } catch (IllegalArgumentException e) {
            // Wrap specific exception in more general one
            throw new RuntimeException("Operation failed", e);
        }
    }

    static void riskyOperation() {
        throw new IllegalArgumentException("Invalid input");
    }

    static void logAndRethrow() {
        try {
            performTask();
        } catch (RuntimeException e) {
            // Log the error
            System.out.println("  [LOG] Error occurred: " + e.getMessage());
            System.out.println("  [LOG] Stack trace logged");

            // Rethrow for higher-level handling
            throw e;
        }
    }

    static void performTask() {
        throw new RuntimeException("Task execution error");
    }

    static void conditionalRethrowDemo() {
        int[] errorCodes = {1, 2, 3};

        for (int code : errorCodes) {
            try {
                throwWithCode(code);
            } catch (RuntimeException e) {
                if (code == 2) {
                    // Recoverable - handle and continue
                    System.out.println("Code " + code + ": Handled (recoverable)");
                } else {
                    // Not recoverable - report but continue demo
                    System.out.println("Code " + code + ": " + e.getMessage() + " (would rethrow)");
                    // In real code: throw e;
                }
            }
        }
    }

    static void throwWithCode(int code) {
        throw new RuntimeException("Error code " + code);
    }
}

//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//

Catch, do something (log), then throw e to propagate.

Exercise: Practical.java

Build a validation system with proper exception throwing