PYnative

Python Programming

  • Learn Python ▼
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises ▼
    • Python Exercises
    • C++ Exercises
    • C Programming Exercises
    • Java Exercises
    • C# Exercises
  • Quizzes
  • Online Compilers▼
    • Online Python Code Editor
    • Online C Compiler
    • Online C++ Compiler
    • Online Java Compiler
    • Online C# Compiler
Home » Java Exercises » Java Exception Handling Exercises: 20 Coding Problems with Solutions

Java Exception Handling Exercises: 20 Coding Problems with Solutions

Updated on: July 11, 2026 | Leave a Comment

This collection of 20 Java exception handling exercises starts with a basic try-catch block and builds all the way up to custom exceptions, chaining, and the subtleties of try-with-resources.

  • Early exercises cover catching basic exceptions, ordering multiple catch blocks correctly, and the guarantees of a finally block.
  • The middle set introduces checked versus unchecked exceptions, the throws keyword, writing your own custom checked and unchecked exception classes, re-throwing, and the Java 7+ multi-catch syntax.
  • The final exercises cover try-with-resources, exception chaining to preserve a root cause, suppressed exceptions during cleanup, a thread’s uncaught exception handler, and the distinction between Exception and Error.

Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation that clarifies exactly which exception is thrown and why.

  • Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
  • Practice questions using our Online Java Compiler
+ Table of Contents (20 Exercises)

Table of contents

  • Exercise 1: Divide-by-Zero Exception
  • Exercise 2: Array Bounds Guard
  • Exercise 3: NumberFormatException Retry Loop
  • Exercise 4: Catching NullPointerException
  • Exercise 5: The Hierarchy Trap (Multiple Catch Blocks)
  • Exercise 6: finally Block Guarantee
  • Exercise 7: Nested Try-Catch Blocks
  • Exercise 8: Age Validator with Explicit throw
  • Exercise 9: throws Keyword Declaration
  • Exercise 10: Checked vs Unchecked Exceptions
  • Exercise 11: Custom Checked Exception: InsufficientFundsException
  • Exercise 12: Custom Unchecked Exception: WeakPasswordException
  • Exercise 13: Re-throwing Exceptions
  • Exercise 14: The Modern Multi-Catch
  • Exercise 15: Modern Cleanup (Try-With-Resources)
  • Exercise 16: Overriding Methods with Checked Exceptions
  • Exercise 17: Exception Chaining
  • Exercise 18: Suppressed Exceptions
  • Exercise 19: Uncaught Exception Handler
  • Exercise 20: Error vs. Exception

Exercise 1: Divide-by-Zero Exception

Problem Statement: Write a program that accepts two integers from the user and divides the first by the second. Use a try-catch block to handle ArithmeticException if the user enters 0 as the denominator, displaying a friendly error message instead of crashing.

Purpose: This exercise introduces the basic try-catch structure, showing how a program can recover from a runtime error instead of terminating abruptly.

Given Input: numerator = 10, denominator = 0

Expected Output: Error: Cannot divide by zero. Please try a non-zero denominator.

▼ Hint
  • Wrap the division statement inside a try block.
  • Add a catch block for ArithmeticException right after it.
  • Print a friendly message inside the catch block instead of letting the exception propagate.
▼ Solution & Explanation
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter numerator: ");
        int numerator = scanner.nextInt();
        System.out.print("Enter denominator: ");
        int denominator = scanner.nextInt();

        try {
            int result = numerator / denominator;
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Error: Cannot divide by zero. Please try a non-zero denominator.");
        }
    }
}Code language: Java (java)

Explanation:

  • try { ... }: Marks the block of code that might throw an exception, in this case the division operation.
  • numerator / denominator: When denominator is 0, integer division throws an ArithmeticException at runtime rather than producing a result.
  • catch (ArithmeticException e): Intercepts the exception before it can crash the program, giving control back to the following statements.
  • Alternative: You could check if (denominator == 0) before dividing, but using try-catch demonstrates handling the failure after it occurs rather than preventing it beforehand.

Exercise 2: Array Bounds Guard

Problem Statement: Create an integer array of size 5. Write a loop that attempts to access and print elements up to index 7. Catch the ArrayIndexOutOfBoundsException and print a message showing exactly which out-of-bounds index caused the failure.

Purpose: This exercise shows how a try-catch block can live inside a loop, letting the loop continue running instead of stopping the entire program the moment one iteration fails.

Given Input: int[] numbers = {10, 20, 30, 40, 50};

Expected Output:

Element at index 0: 10
Element at index 1: 20
Element at index 2: 30
Element at index 3: 40
Element at index 4: 50
Error: Index 5 is out of bounds for this array.
Error: Index 6 is out of bounds for this array.
Error: Index 7 is out of bounds for this array.
▼ Hint
  • Place the try-catch block inside the loop body, not around the whole loop, so it runs on every iteration.
  • Loop the index from 0 to 7 inclusive, even though the array only holds 5 elements.
  • Use the loop variable i directly in the error message so it reflects the exact index that failed.
▼ Solution & Explanation
public class Main {
    public static void main(String[] args) {
        int[] numbers = {10, 20, 30, 40, 50};

        for (int i = 0; i <= 7; i++) {
            try {
                System.out.println("Element at index " + i + ": " + numbers[i]);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Error: Index " + i + " is out of bounds for this array.");
            }
        }
    }
}Code language: Java (java)

Explanation:

  • for (int i = 0; i <= 7; i++): Deliberately loops past the array’s valid range, which only spans indices 0 through 4.
  • numbers[i]: Throws ArrayIndexOutOfBoundsException once i reaches 5, since that index does not exist in a 5-element array.
  • catch (ArrayIndexOutOfBoundsException e): Catches the failure for that single iteration only, allowing the loop to continue to the next value of i.
  • "Error: Index " + i + " is out of bounds...": Reports the exact failing index by reading the loop variable, rather than relying on the exception’s own message text.

Exercise 3: NumberFormatException Retry Loop

Problem Statement: Prompt the user to enter their age as a string. Attempt to parse it into an integer using Integer.parseInt(). Catch NumberFormatException and continuously prompt the user until they enter a valid numeric value.

Purpose: This exercise combines exception handling with a loop to build a validation retry pattern, a technique used constantly when reading and sanitizing user input.

Given Input: User enters "abc", then "25"

Expected Output:

Invalid input. Please enter a numeric value.
Your age is: 25
▼ Hint
  • Use a boolean flag to control a while loop that keeps prompting until parsing succeeds.
  • Set the flag to true only after Integer.parseInt() completes without throwing.
  • Print a retry message inside the catch block instead of exiting the loop.
▼ Solution & Explanation
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int age = 0;
        boolean valid = false;

        while (!valid) {
            System.out.print("Enter your age: ");
            String input = scanner.nextLine();
            try {
                age = Integer.parseInt(input);
                valid = true;
            } catch (NumberFormatException e) {
                System.out.println("Invalid input. Please enter a numeric value.");
            }
        }
        System.out.println("Your age is: " + age);
    }
}Code language: Java (java)

Explanation:

  • while (!valid): Keeps the prompt loop running as long as a valid number has not yet been entered.
  • Integer.parseInt(input): Throws NumberFormatException when the input string cannot be converted to an integer, such as "abc".
  • valid = true: Only runs when parsing succeeds, since an exception would skip the rest of the try block and jump straight to catch.
  • catch (NumberFormatException e): Prints a retry message and lets the loop repeat, since valid is still false at that point.

Exercise 4: Catching NullPointerException

Problem Statement: Create a method String toUpperCase(String str). Inside, purposely do not check for null natively; instead, wrap the .toUpperCase() call in a try-catch block. Catch NullPointerException, print a warning, and return a default string like "EMPTY".

Purpose: This exercise shows how NullPointerException, one of the most common runtime errors in Java, can be caught like any other exception, even though an explicit if (str == null) check is usually the better style in production code.

Given Input: toUpperCase("hello"); toUpperCase(null);

Expected Output:

HELLO
Warning: Received a null string.
EMPTY
▼ Hint
  • Call str.toUpperCase() directly inside the try block without checking str first.
  • When str is null, calling any method on it throws NullPointerException automatically.
  • In the catch block, print a warning and return "EMPTY" instead of the uppercase result.
▼ Solution & Explanation
public class Main {
    public static String toUpperCase(String str) {
        try {
            return str.toUpperCase();
        } catch (NullPointerException e) {
            System.out.println("Warning: Received a null string.");
            return "EMPTY";
        }
    }

    public static void main(String[] args) {
        System.out.println(toUpperCase("hello"));
        System.out.println(toUpperCase(null));
    }
}Code language: Java (java)

Explanation:

  • str.toUpperCase(): Calling a method on a null reference throws NullPointerException before the method body can do anything with it.
  • catch (NullPointerException e): Intercepts that failure, printing a warning and substituting a safe default value instead of letting the exception propagate.
  • return "EMPTY": Ensures the method always returns a usable String, even when the input was invalid.
  • Alternative: A cleaner, more idiomatic version would check if (str == null) return "EMPTY"; up front, avoiding the exception entirely instead of relying on catching it.

Exercise 5: The Hierarchy Trap (Multiple Catch Blocks)

Problem Statement: Write a code snippet that intentionally triggers both an ArrayIndexOutOfBoundsException and an ArithmeticException. Write multiple catch blocks. Bonus challenge: Intentional order violation. Try catching Exception before ArithmeticException and observe the compiler error. Fix it by ordering them from specific to general.

Purpose: This exercise demonstrates how multiple catch blocks are evaluated top to bottom, and why the Java compiler enforces that more specific exception types must be caught before more general ones.

Given Input: int[] numbers = {1, 2, 3}; accessed at index 5, then 10 / 0

Expected Output:

Caught: Array index out of bounds.
Caught: Arithmetic error.
▼ Hint
  • Order the catch blocks from most specific to most general: ArrayIndexOutOfBoundsException, then ArithmeticException, then Exception.
  • Java only allows the first matching catch block to run, and only the first one that fits is checked.
  • Placing catch (Exception e) before catch (ArithmeticException e) makes the more specific block unreachable, which the compiler rejects outright.
▼ Solution & Explanation
public class Main {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};

        try {
            System.out.println(numbers[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Caught: Array index out of bounds.");
        } catch (ArithmeticException e) {
            System.out.println("Caught: Arithmetic error.");
        } catch (Exception e) {
            System.out.println("Caught: Some other exception.");
        }

        try {
            int result = 10 / 0;
            System.out.println(result);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Caught: Array index out of bounds.");
        } catch (ArithmeticException e) {
            System.out.println("Caught: Arithmetic error.");
        } catch (Exception e) {
            System.out.println("Caught: Some other exception.");
        }
    }
}Code language: Java (java)

Explanation:

  • catch (ArrayIndexOutOfBoundsException e) first: Placed before the broader catch blocks, so it handles the first snippet’s out-of-bounds access precisely.
  • catch (ArithmeticException e) second: Handles the divide-by-zero error in the second snippet, since ArrayIndexOutOfBoundsException does not match it.
  • catch (Exception e) last: Acts as a catch-all for anything not matched by the earlier, more specific blocks, and never actually runs in this example.
  • Bonus: Reordering the blocks so catch (Exception e) comes before catch (ArithmeticException e) produces a compile-time error, because ArithmeticException is a subclass of Exception and would already be handled by the earlier, broader block, making the specific one unreachable.

Exercise 6: finally Block Guarantee

Problem Statement: Create a method that opens a try block, prints a message, and immediately executes a return statement. Add a finally block that prints "I am inevitable". Verify whether the finally block executes even though the method returned early.

Purpose: This exercise demonstrates a key guarantee of the try-finally structure: the finally block always runs before control actually leaves the method, even when a return statement fires inside the try block.

Given Input: int result = demoFinally();

Expected Output:

Inside try block
I am inevitable
Returned value: 1
▼ Hint
  • A finally block runs after the try block finishes, regardless of whether it completed normally, threw an exception, or hit a return statement.
  • The return value is determined before finally runs, but finally still executes before the method actually hands control back to the caller.
  • No catch block is needed here, since nothing is being caught. A try can pair directly with finally.
▼ Solution & Explanation
public class Main {
    public static int demoFinally() {
        try {
            System.out.println("Inside try block");
            return 1;
        } finally {
            System.out.println("I am inevitable");
        }
    }

    public static void main(String[] args) {
        int result = demoFinally();
        System.out.println("Returned value: " + result);
    }
}Code language: Java (java)

Explanation:

  • try { ... return 1; }: The return 1 statement schedules the method to return the value 1, but does not exit immediately.
  • finally { System.out.println("I am inevitable"); }: Runs before the method actually returns, printing its message even though a return was already triggered inside try.
  • Order of output: "Inside try block" prints first, then "I am inevitable" from finally, and only after that does demoFinally() actually return 1 to the caller.
  • Alternative: The same behavior holds even if the try block throws an exception instead of returning: finally still runs first, before the exception propagates further.

Exercise 7: Nested Try-Catch Blocks

Problem Statement: Write a program with a nested try-catch structure. The outer block should handle a NumberFormatException, while the inner block handles an ArithmeticException. Trigger the inner exception, catch it, and then intentionally trigger the outer exception from inside the inner catch block.

Purpose: This exercise shows that a catch block is ordinary code that can itself throw a new exception, and that an outer try-catch can handle a failure that originates from inside an inner catch block.

Given Input: none (the exceptions are triggered directly in code)

Expected Output:

Outer try started
Inner try started
Inner catch: Arithmetic exception caught.
Outer catch: Number format exception caught.
▼ Hint
  • Nest a full try-catch for ArithmeticException inside the try block of the outer try-catch for NumberFormatException.
  • Trigger the inner exception with something like 10 / 0.
  • Inside the inner catch block, call Integer.parseInt() on an invalid string to trigger the outer exception.
▼ Solution & Explanation
public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("Outer try started");

            try {
                System.out.println("Inner try started");
                int result = 10 / 0;
                System.out.println(result);
            } catch (ArithmeticException e) {
                System.out.println("Inner catch: Arithmetic exception caught.");
                int value = Integer.parseInt("abc");
                System.out.println(value);
            }

        } catch (NumberFormatException e) {
            System.out.println("Outer catch: Number format exception caught.");
        }
    }
}Code language: Java (java)

Explanation:

  • 10 / 0: Throws ArithmeticException inside the inner try block, which is immediately caught by the inner catch.
  • Integer.parseInt("abc"): Called from inside the inner catch block, this throws a new NumberFormatException since "abc" is not a valid number.
  • Propagation: Because the inner try-catch only handles ArithmeticException, the new NumberFormatException is not caught locally and propagates up to the outer catch block instead.
  • catch (NumberFormatException e) (outer): Finally catches the exception that originated inside the inner catch block, completing the nested handling chain.

Exercise 8: Age Validator with Explicit throw

Problem Statement: Write a method validateAge(int age) that throws an IllegalArgumentException with the message "Access Denied: Under 18" if the age is less than 18. Call this method in main and handle the exception.

Purpose: This exercise practices manually throwing an exception with throw, rather than relying on the JVM to raise one, which is how custom validation rules are typically enforced in Java.

Given Input: validateAge(15);

Expected Output: Error: Access Denied: Under 18

▼ Hint
  • Use an if statement to check the condition, and throw new IllegalArgumentException("...") inside it.
  • Wrap the method call in main with a try-catch block.
  • Use e.getMessage() inside the catch block to retrieve the message passed to the exception’s constructor.
▼ Solution & Explanation
public class Main {
    public static void validateAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("Access Denied: Under 18");
        }
        System.out.println("Access Granted: Age " + age + " is valid.");
    }

    public static void main(String[] args) {
        try {
            validateAge(15);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • throw new IllegalArgumentException("Access Denied: Under 18"): Manually creates and throws an exception object, immediately stopping normal execution of validateAge().
  • IllegalArgumentException: A standard unchecked exception commonly used to signal that a method argument does not meet its requirements.
  • catch (IllegalArgumentException e): Catches the thrown exception in main, preventing it from crashing the program.
  • e.getMessage(): Retrieves the exact message string passed into the exception’s constructor, which is then combined with "Error: " for the final output.

Exercise 9: throws Keyword Declaration

Problem Statement: Create a method readFile(String path) that attempts to use a FileReader. Because FileReader throws a checked FileNotFoundException, do not use a try-catch inside the method. Instead, use the throws keyword in the method signature to pass the responsibility to the caller.

Purpose: This exercise introduces checked exceptions and the throws keyword, showing that a method is allowed to declare a checked exception instead of handling it, shifting the obligation to whoever calls it.

Given Input: readFile("nonexistent.txt");

Expected Output: Error: File not found or unreadable.

▼ Hint
  • Add throws IOException (or the more specific FileNotFoundException) to the readFile method signature.
  • Do not wrap the FileReader creation in a try-catch inside readFile itself.
  • The caller in main is the one responsible for wrapping the call in a try-catch.
▼ Solution & Explanation
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void readFile(String path) throws IOException {
        FileReader reader = new FileReader(path);
        reader.close();
    }

    public static void main(String[] args) {
        try {
            readFile("nonexistent.txt");
        } catch (IOException e) {
            System.out.println("Error: File not found or unreadable.");
        }
    }
}Code language: Java (java)

Explanation:

  • public static void readFile(String path) throws IOException: The throws clause declares that this method may produce a checked exception without handling it internally, forcing every caller to deal with it.
  • new FileReader(path): Throws FileNotFoundException, a subclass of IOException, when the given file does not exist at that path.
  • readFile("nonexistent.txt") inside try: Since readFile declares throws IOException, the compiler requires main to either catch it or declare its own throws clause.
  • catch (IOException e): Catches the exception at the point where the caller decides how to respond, rather than inside readFile itself.

Exercise 10: Checked vs Unchecked Exceptions

Problem Statement: Write a class with two methods: methodA() which throws a checked exception (IOException), and methodB() which throws an unchecked exception (NullPointerException). Write a main method that calls both, demonstrating why the compiler forces you to handle or declare methodA but lets methodB slide.

Purpose: This exercise closes out the series by directly contrasting checked and unchecked exceptions side by side, making clear which category the compiler actively enforces and which it leaves entirely to the programmer’s discipline.

Given Input: methodA(); methodB();

Expected Output:

Caught checked exception: Simulated I/O failure
Caught unchecked exception: Simulated null reference
▼ Hint
  • methodA() needs a throws IOException clause, since IOException is a checked exception the compiler tracks.
  • methodB() needs no throws clause at all, since NullPointerException is unchecked and the compiler does not require it to be declared.
  • Try removing the try-catch around the methodA() call and see that it fails to compile, then try the same with methodB() and see that it still compiles fine.
▼ Solution & Explanation
import java.io.IOException;

public class Main {
    public static void methodA() throws IOException {
        throw new IOException("Simulated I/O failure");
    }

    public static void methodB() {
        throw new NullPointerException("Simulated null reference");
    }

    public static void main(String[] args) {
        try {
            methodA();
        } catch (IOException e) {
            System.out.println("Caught checked exception: " + e.getMessage());
        }

        try {
            methodB();
        } catch (NullPointerException e) {
            System.out.println("Caught unchecked exception: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • methodA() throws IOException: IOException is a checked exception, so the compiler forces any caller to either catch it or declare it in their own throws clause.
  • methodB() with no throws clause: NullPointerException extends RuntimeException, making it unchecked, so the compiler never requires it to be caught or declared.
  • First try-catch around methodA(): Mandatory here. Without it, the code simply would not compile because of the checked throws IOException declaration.
  • Second try-catch around methodB(): Optional from the compiler’s perspective, since methodB() would still compile without it, but it is included here so the program does not crash when the exception is actually thrown.

Exercise 11: Custom Checked Exception: InsufficientFundsException

Problem Statement: Simulate a simple banking system. Create a custom checked exception named InsufficientFundsException (extending Exception). Write a BankAccount class with a withdraw(double amount) method that throws this exception if the withdrawal amount exceeds the balance.

Purpose: This exercise introduces creating your own checked exception class, showing how domain-specific errors like a failed withdrawal can be modeled as first-class exception types instead of generic ones.

Given Input: BankAccount account = new BankAccount(500.0); account.withdraw(700.0);

Expected Output: Error: Insufficient funds: balance is 500.0, requested 700.0

▼ Hint
  • Extend Exception directly, and pass the message through to the parent constructor with super(message).
  • Since it extends Exception rather than RuntimeException, the withdraw() method must declare throws InsufficientFundsException.
  • Compare amount against the balance before subtracting, and throw before making any changes to the account state.
▼ Solution & Explanation
class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException("Insufficient funds: balance is " + balance + ", requested " + amount);
        }
        balance -= amount;
        System.out.println("Withdrawal successful. Remaining balance: " + balance);
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(500.0);
        try {
            account.withdraw(700.0);
        } catch (InsufficientFundsException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • class InsufficientFundsException extends Exception: Creates a custom checked exception, meaning any method that can throw it must either catch it or declare it with throws.
  • super(message): Forwards the custom message to the Exception parent class, so it becomes available later through getMessage().
  • public void withdraw(double amount) throws InsufficientFundsException: Declares the checked exception on the method signature, since it is not caught internally.
  • account.withdraw(700.0): Fails because the requested amount exceeds the account’s balance of 500.0, triggering the custom exception with a descriptive message.

Exercise 12: Custom Unchecked Exception: WeakPasswordException

Problem Statement: Create a custom unchecked exception named WeakPasswordException (extending RuntimeException). Write a registration utility that checks if a user’s password is at least 8 characters long and contains a number. Throw the exception if it fails the criteria.

Purpose: This exercise contrasts with the previous one by creating an unchecked custom exception, appropriate for validation failures that are considered programming or input errors rather than recoverable conditions the caller must be forced to handle.

Given Input: RegistrationUtil.validatePassword("abc123");

Expected Output: Registration failed: Password must be at least 8 characters long and contain a number.

▼ Hint
  • Extend RuntimeException instead of Exception, so callers are not forced to catch or declare it.
  • Use String.length() to check the minimum length requirement.
  • Use a regular expression like ".*\\d.*" with matches() to check for at least one digit.
▼ Solution & Explanation
class WeakPasswordException extends RuntimeException {
    public WeakPasswordException(String message) {
        super(message);
    }
}

class RegistrationUtil {
    public static void validatePassword(String password) {
        boolean hasDigit = password.matches(".*\\d.*");
        if (password.length() < 8 || !hasDigit) {
            throw new WeakPasswordException("Password must be at least 8 characters long and contain a number.");
        }
        System.out.println("Password accepted.");
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            RegistrationUtil.validatePassword("abc123");
        } catch (WeakPasswordException e) {
            System.out.println("Registration failed: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • class WeakPasswordException extends RuntimeException: Makes this an unchecked exception, so validatePassword() does not need a throws declaration.
  • password.matches(".*\\d.*"): Returns true if the password contains at least one digit anywhere in the string.
  • password.length() < 8 || !hasDigit: Combines both validation rules, throwing the exception if either the length requirement or the digit requirement is not met.
  • validatePassword("abc123"): Fails because the password is only 6 characters long, even though it does contain digits, triggering the exception with the combined requirement message.

Exercise 13: Re-throwing Exceptions

Problem Statement: Write a method that catches a SQLException (you can mock this or use any checked exception). Log the message locally by printing "Log: Exception captured", and then use the throw keyword to re-throw that exact same exception object up the call stack.

Purpose: This exercise shows that a catch block does not have to fully resolve an exception. It can perform local work, like logging, and still forward the original exception object to the caller for further handling.

Given Input: queryDatabase();

Expected Output:

Log: Exception captured
Main caught: Connection timed out
▼ Hint
  • Since SQLException is checked, the method that catches and re-throws it needs its own throws SQLException declaration.
  • Print the log message first inside the catch block.
  • Use throw e; to re-throw the exact same exception object, rather than constructing a new one.
▼ Solution & Explanation
import java.sql.SQLException;

public class Main {
    public static void queryDatabase() throws SQLException {
        try {
            throw new SQLException("Connection timed out");
        } catch (SQLException e) {
            System.out.println("Log: Exception captured");
            throw e;
        }
    }

    public static void main(String[] args) {
        try {
            queryDatabase();
        } catch (SQLException e) {
            System.out.println("Main caught: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • catch (SQLException e) { ...; throw e; }: Catches the exception long enough to log it locally, then re-throws the same object so the caller still receives it.
  • public static void queryDatabase() throws SQLException: Still required even though the exception is caught inside the method, because it is re-thrown rather than fully handled.
  • throw e;: Preserves the original exception object, including its stack trace, instead of wrapping it in a new exception.
  • Order of output: "Log: Exception captured" prints first from inside queryDatabase(), then "Main caught: ..." prints once the same exception reaches the outer catch block in main.

Exercise 14: The Modern Multi-Catch

Problem Statement: Write a program that can potentially throw a ParseException or an IOException. Instead of writing two separate catch blocks, use the Java 7+ multi-catch syntax (catch (ParseException | IOException e)) to handle both in a single block using a bitwise OR | operator.

Purpose: This exercise practices the multi-catch syntax, which reduces duplicated handling code when several unrelated exception types should be treated the same way.

Given Input: processInput("not-a-date", false);

Expected Output: Caught: ParseException - Unparseable date: "not-a-date"

▼ Hint
  • Declare both exceptions in a single catch parameter separated by |: catch (ParseException | IOException e).
  • Inside the block, e is typed as the common supertype of both, so only members shared by both exception classes are directly accessible.
  • Use SimpleDateFormat.parse() on an invalid date string to trigger a ParseException.
▼ Solution & Explanation
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Main {
    public static void processInput(String dateStr, boolean simulateIOError) throws ParseException, IOException {
        if (simulateIOError) {
            throw new IOException("Simulated I/O failure");
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        format.parse(dateStr);
        System.out.println("Parsed successfully: " + dateStr);
    }

    public static void main(String[] args) {
        try {
            processInput("not-a-date", false);
        } catch (ParseException | IOException e) {
            System.out.println("Caught: " + e.getClass().getSimpleName() + " - " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • throws ParseException, IOException: Declares that processInput() may throw either checked exception, depending on which branch of the method runs.
  • catch (ParseException | IOException e): A single block that handles both exception types identically, avoiding two nearly identical catch blocks.
  • format.parse(dateStr): Throws ParseException because "not-a-date" does not match the expected "yyyy-MM-dd" pattern.
  • e.getClass().getSimpleName(): Confirms which of the two exception types was actually thrown, since the catch parameter alone does not distinguish them at compile time.

Exercise 15: Modern Cleanup (Try-With-Resources)

Problem Statement: Create a custom resource class named MockDatabaseConnection that implements the AutoCloseable interface. Override the close() method to print "Connection closed safely". Write a main method that uses try-with-resources to open this connection and witness it auto-closing without an explicit finally block.

Purpose: This exercise introduces try-with-resources, which automatically calls close() on any resource declared in its parentheses once the block finishes, removing the need for a manual finally block.

Given Input: none (the resource is opened and closed automatically)

Expected Output:

Connection opened
Executing: SELECT * FROM users
Connection closed safely
▼ Hint
  • Implement AutoCloseable and override its single abstract method, close().
  • Declare the resource inside the parentheses of the try statement: try (MockDatabaseConnection connection = new MockDatabaseConnection()).
  • No catch or finally block is required. The resource still closes automatically once the try block ends.
▼ Solution & Explanation
class MockDatabaseConnection implements AutoCloseable {
    public MockDatabaseConnection() {
        System.out.println("Connection opened");
    }

    public void query(String sql) {
        System.out.println("Executing: " + sql);
    }

    @Override
    public void close() {
        System.out.println("Connection closed safely");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MockDatabaseConnection connection = new MockDatabaseConnection()) {
            connection.query("SELECT * FROM users");
        }
    }
}Code language: Java (java)

Explanation:

  • implements AutoCloseable: Marks the class as a valid resource type that can be declared inside a try-with-resources statement.
  • try (MockDatabaseConnection connection = new MockDatabaseConnection()): Creates the resource as part of the try statement itself, rather than before it.
  • close(): Called automatically by the JVM once the try block finishes, whether it completes normally or exits due to an exception.
  • No finally block: Not needed here, since try-with-resources guarantees the cleanup call on its own.

Exercise 16: Overriding Methods with Checked Exceptions

Problem Statement: Create a parent class Parent with a method void processData() throws IOException. Create a child class Child that overrides processData().

  • Test what happens if the child tries to throw a broader exception like Exception.
  • Test what happens if the child throws a more specific one like FileNotFoundException.
  • Test what happens if the child throws an unchecked exception.

Purpose: This exercise explores how method overriding interacts with checked exceptions, a rule that exists so code calling through a Parent reference can never be surprised by a checked exception it did not already expect.

Given Input: Parent obj = new Child(); obj.processData();

Expected Output: Child processing data with a narrower exception

▼ Hint
  • An overriding method can declare the same checked exception, a subclass of it, or no checked exception at all.
  • It cannot declare a broader checked exception than the parent method, since that would break code relying on the parent’s contract.
  • Unchecked exceptions are never restricted by these rules and can always be added freely, since the compiler does not track them.
▼ Solution & Explanation
import java.io.FileNotFoundException;
import java.io.IOException;

class Parent {
    void processData() throws IOException {
        System.out.println("Parent processing data");
    }
}

class Child extends Parent {
    @Override
    void processData() throws FileNotFoundException {
        System.out.println("Child processing data with a narrower exception");
    }

    // Would NOT compile: declaring a broader checked exception than the parent.
    // @Override
    // void processData() throws Exception { }

    // Compiles fine: unchecked exceptions are never restricted by overriding rules.
    // @Override
    // void processData() { throw new RuntimeException("Unchecked is always allowed"); }
}

public class Main {
    public static void main(String[] args) {
        Parent obj = new Child();
        try {
            obj.processData();
        } catch (IOException e) {
            System.out.println("Caught: " + e.getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • Broader exception (Exception): Fails to compile. Since Parent.processData() only promises IOException, code calling it through a Parent reference is never prepared for a wider checked exception.
  • Narrower exception (FileNotFoundException): Compiles successfully, since FileNotFoundException is a subclass of IOException and still fits within the parent’s original contract.
  • Unchecked exception: Always compiles, regardless of the parent’s throws clause, because unchecked exceptions are not part of the checked-exception contract the compiler enforces during overriding.
  • Parent obj = new Child(); obj.processData();: Demonstrates polymorphism, where the child’s narrower, valid override runs even though the reference type is Parent.

Exercise 17: Exception Chaining

Problem Statement: Write a database adapter method. Catch a low-level SQLException (or a simulated IOException). Instead of losing the stack trace, wrap it inside a higher-level business exception like DataAccessException by passing the original exception into the new exception’s constructor (throw new DataAccessException("Fetch failed", e);).

Purpose: This exercise practices exception chaining, which lets a higher-level, more meaningful exception replace a low-level one at the API boundary while still preserving the original cause for debugging.

Given Input: adapter.fetchRecord();

Expected Output:

Error: Fetch failed
Caused by: Connection reset by peer
▼ Hint
  • Give DataAccessException a constructor that accepts both a message and a Throwable cause, and forwards both to super(message, cause).
  • Inside the catch block for the low-level exception, construct the new exception and pass the caught exception in as the cause.
  • Use e.getCause() at the top level to retrieve the original low-level exception from the wrapper.
▼ Solution & Explanation
import java.io.IOException;

class DataAccessException extends RuntimeException {
    public DataAccessException(String message, Throwable cause) {
        super(message, cause);
    }
}

class DatabaseAdapter {
    public void fetchRecord() {
        try {
            throw new IOException("Connection reset by peer");
        } catch (IOException e) {
            throw new DataAccessException("Fetch failed", e);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        DatabaseAdapter adapter = new DatabaseAdapter();
        try {
            adapter.fetchRecord();
        } catch (DataAccessException e) {
            System.out.println("Error: " + e.getMessage());
            System.out.println("Caused by: " + e.getCause().getMessage());
        }
    }
}Code language: Java (java)

Explanation:

  • super(message, cause): Passes both the new, higher-level message and the original exception to the built-in Throwable constructor that supports chaining.
  • throw new DataAccessException("Fetch failed", e): Wraps the low-level IOException inside a business-level exception, replacing it in the thrown type while keeping a reference to it.
  • e.getCause(): Retrieves the original IOException from inside the DataAccessException, giving access to the true root cause of the failure.
  • Why this matters: Without chaining, catching and re-throwing a brand new exception without the cause would silently discard the original stack trace, making the real failure much harder to diagnose later.

Exercise 18: Suppressed Exceptions

Problem Statement: Using try-with-resources, create a scenario where the code inside the try block throws an exception, and the close() method of the resource also throws an exception. Catch the primary exception in main and use e.getSuppressed() to extract and print the hidden exception thrown during cleanup.

Purpose: This exercise reveals a subtle behavior of try-with-resources: when both the body and close() throw, only the body’s exception propagates as the primary one, while the close() exception is attached to it as a suppressed exception rather than lost.

Given Input: try (RiskyResource resource = new RiskyResource()) { resource.use(); }

Expected Output:

Primary exception: Failure during use
Suppressed exception: Failure during close
▼ Hint
  • Make both use() and close() throw a RuntimeException with different messages.
  • The exception thrown by the try block body becomes the primary exception that catch receives.
  • Call e.getSuppressed() on the caught exception. It returns an array of any exceptions that were suppressed during resource cleanup.
▼ Solution & Explanation
class RiskyResource implements AutoCloseable {
    public void use() {
        throw new RuntimeException("Failure during use");
    }

    @Override
    public void close() {
        throw new RuntimeException("Failure during close");
    }
}

public class Main {
    public static void main(String[] args) {
        try (RiskyResource resource = new RiskyResource()) {
            resource.use();
        } catch (RuntimeException e) {
            System.out.println("Primary exception: " + e.getMessage());
            for (Throwable suppressed : e.getSuppressed()) {
                System.out.println("Suppressed exception: " + suppressed.getMessage());
            }
        }
    }
}Code language: Java (java)

Explanation:

  • resource.use(): Throws first, while the try block body is still executing, so this becomes the primary exception.
  • close(): Still gets called automatically as the resource is torn down, even though the body already failed. Its own exception would normally replace the original one and hide it.
  • e.getSuppressed(): Instead of losing the close() exception, Java attaches it to the primary exception’s suppressed list, accessible through this method.
  • Why this matters: Without suppression, a failure during cleanup could silently overwrite the more important original failure, making the true root cause disappear entirely.

Exercise 19: Uncaught Exception Handler

Problem Statement: Write a program that starts a separate background thread. Intentionally crash that thread with a NullPointerException. Before running it, implement and register a Thread.UncaughtExceptionHandler globally or for that specific thread to elegantly log the crash instead of letting it dump to System.err.

Purpose: This exercise shows that try-catch around a thread’s start() call cannot catch exceptions thrown inside that thread, since it runs independently, and that UncaughtExceptionHandler is the correct mechanism for handling thread-level crashes.

Given Input: worker.start(); worker.join();

Expected Output:

Caught crash in thread 'Thread-0': Simulated crash in worker thread
Main thread continues normally
▼ Hint
  • A regular try-catch wrapped around worker.start() will not catch exceptions thrown inside the thread’s own run logic, since start() only launches the thread and returns immediately.
  • Call worker.setUncaughtExceptionHandler(...) before starting the thread, passing a handler that logs the thread name and exception message.
  • Call worker.join() in main so the program waits for the background thread to finish before continuing.
▼ Solution & Explanation
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            throw new NullPointerException("Simulated crash in worker thread");
        });

        worker.setUncaughtExceptionHandler((thread, exception) -> {
            System.out.println("Caught crash in thread '" + thread.getName() + "': " + exception.getMessage());
        });

        worker.start();
        worker.join();
        System.out.println("Main thread continues normally");
    }
}Code language: Java (java)

Explanation:

  • new Thread(() -> { throw new NullPointerException(...); }): Defines a thread whose task immediately throws an unchecked exception once it starts running.
  • worker.setUncaughtExceptionHandler(...): Registers a callback that the JVM invokes automatically if the thread terminates due to an uncaught exception, instead of printing a raw stack trace to System.err.
  • worker.join(): Pauses the main thread until the worker thread finishes, ensuring the crash log prints before "Main thread continues normally".
  • Why try-catch would not work here: start() only schedules the thread to run and returns right away, so any exception thrown later inside the thread happens on a separate call stack that a surrounding try-catch in main can never see.

Exercise 20: Error vs. Exception

Problem Statement: Write a program that deliberately triggers an OutOfMemoryError (e.g., creating an infinitely expanding list of large objects) or a StackOverflowError (infinite recursion). Try catching it using catch (Exception e). Observe why it fails to catch, and rewrite the catch block using Throwable or Error to intercept it (and discuss why catching Errors is generally bad practice).

Purpose: This closing exercise clarifies the split at the very top of the exception hierarchy: Exception and Error are separate branches under Throwable, so a catch (Exception e) block can never catch an Error, no matter how broad it looks.

Given Input: recurse(); (infinite recursion with no base case)

Expected Output: Caught: StackOverflowError

▼ Hint
  • Write a method that calls itself with no base case or termination condition.
  • First try wrapping the call in catch (Exception e) and notice the program still crashes with an uncaught error.
  • StackOverflowError extends Error, not Exception, so the catch block needs to target Error or the common parent Throwable instead.
▼ Solution & Explanation
public class Main {
    public static void recurse() {
        recurse();
    }

    public static void main(String[] args) {
        // catch (Exception e) here would NOT catch the crash below,
        // because StackOverflowError extends Error, not Exception.
        try {
            recurse();
        } catch (Error e) {
            System.out.println("Caught: " + e.getClass().getSimpleName());
        }
    }
}Code language: Java (java)

Explanation:

  • recurse() calling itself with no base case: Keeps adding frames to the call stack until the JVM runs out of stack space and throws StackOverflowError.
  • Why catch (Exception e) fails: Throwable has two direct subclasses, Exception and Error, as separate branches. Since StackOverflowError descends from Error, it is not an Exception and slips right past a catch (Exception e) block.
  • catch (Error e): Successfully intercepts the crash, since StackOverflowError is a direct subclass of Error.
  • Why catching Errors is generally discouraged: An Error like OutOfMemoryError or StackOverflowError usually signals that the JVM itself is in a corrupted or unstable state. Catching it rarely allows the program to recover safely, and it is typically better to let the JVM terminate than to continue running in an unpredictable condition.

Filed Under: Java Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

Java Exercises

All Coding Exercises:

C Exercises
C++ Exercises
Python Exercises
Java Exercises
C# Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 25+ questions
  • Each Quiz contains 25 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: Java Exercises
TweetF  sharein  shareP  Pin

  Java Exercises

  • All Java Exercises
  • Java Exercise for Beginners
  • Java Loops Exercise
  • Java String Exercise
  • Java ArrayList Exercise
  • Java LinkedList Exercise
  • Java HashMap and TreeMap Exercise
  • Java HashSet and TreeSet Exercise
  • Java OOP Exercise
  • Java Methods Exercise
  • Java Enums Exercise
  • Java Exception Handling Exercise
  • Java File Handling Exercise
  • Java Date and Time Exercise
  • Java Data Structures Exercise
  • Java Sorting and Searching Exercise
  • Java Lambda and Functional Interfaces Exercise
  • Java Regex Exercise
  • Java Random Data Generation Exercise
  • Java Generics Exercise
  • Java Reflection Exercise
  • Java JDBC Exercise

All Coding Exercises

Python Exercises C Exercises C++ Exercises Java Exercises C# Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Coding Exercises

  • C Exercises
  • C++ Exercises
  • Python Exercises
  • Java Exercises
  • C# Exercises

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2026 pynative.com