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
finallyblock. - The middle set introduces checked versus unchecked exceptions, the
throwskeyword, 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 betweenExceptionandError.
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
tryblock. - Add a
catchblock forArithmeticExceptionright after it. - Print a friendly message inside the
catchblock instead of letting the exception propagate.
▼ Solution & Explanation
Explanation:
try { ... }: Marks the block of code that might throw an exception, in this case the division operation.numerator / denominator: Whendenominatoris0, integer division throws anArithmeticExceptionat 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 usingtry-catchdemonstrates 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-catchblock inside the loop body, not around the whole loop, so it runs on every iteration. - Loop the index from
0to7inclusive, even though the array only holds 5 elements. - Use the loop variable
idirectly in the error message so it reflects the exact index that failed.
▼ Solution & Explanation
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]: ThrowsArrayIndexOutOfBoundsExceptiononceireaches 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 ofi."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
booleanflag to control awhileloop that keeps prompting until parsing succeeds. - Set the flag to
trueonly afterInteger.parseInt()completes without throwing. - Print a retry message inside the
catchblock instead of exiting the loop.
▼ Solution & Explanation
Explanation:
while (!valid): Keeps the prompt loop running as long as a valid number has not yet been entered.Integer.parseInt(input): ThrowsNumberFormatExceptionwhen 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 thetryblock and jump straight tocatch.catch (NumberFormatException e): Prints a retry message and lets the loop repeat, sincevalidis stillfalseat 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 thetryblock without checkingstrfirst. - When
strisnull, calling any method on it throwsNullPointerExceptionautomatically. - In the
catchblock, print a warning and return"EMPTY"instead of the uppercase result.
▼ Solution & Explanation
Explanation:
str.toUpperCase(): Calling a method on anullreference throwsNullPointerExceptionbefore 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 usableString, 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
catchblocks from most specific to most general:ArrayIndexOutOfBoundsException, thenArithmeticException, thenException. - Java only allows the first matching
catchblock to run, and only the first one that fits is checked. - Placing
catch (Exception e)beforecatch (ArithmeticException e)makes the more specific block unreachable, which the compiler rejects outright.
▼ Solution & Explanation
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, sinceArrayIndexOutOfBoundsExceptiondoes 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 beforecatch (ArithmeticException e)produces a compile-time error, becauseArithmeticExceptionis a subclass ofExceptionand 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
finallyblock runs after thetryblock finishes, regardless of whether it completed normally, threw an exception, or hit areturnstatement. - The return value is determined before
finallyruns, butfinallystill executes before the method actually hands control back to the caller. - No
catchblock is needed here, since nothing is being caught. Atrycan pair directly withfinally.
▼ Solution & Explanation
Explanation:
try { ... return 1; }: Thereturn 1statement schedules the method to return the value1, but does not exit immediately.finally { System.out.println("I am inevitable"); }: Runs before the method actually returns, printing its message even though areturnwas already triggered insidetry.- Order of output:
"Inside try block"prints first, then"I am inevitable"fromfinally, and only after that doesdemoFinally()actually return1to the caller. - Alternative: The same behavior holds even if the
tryblock throws an exception instead of returning:finallystill 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-catchforArithmeticExceptioninside thetryblock of the outertry-catchforNumberFormatException. - Trigger the inner exception with something like
10 / 0. - Inside the inner
catchblock, callInteger.parseInt()on an invalid string to trigger the outer exception.
▼ Solution & Explanation
Explanation:
10 / 0: ThrowsArithmeticExceptioninside the innertryblock, which is immediately caught by the innercatch.Integer.parseInt("abc"): Called from inside the innercatchblock, this throws a newNumberFormatExceptionsince"abc"is not a valid number.- Propagation: Because the inner
try-catchonly handlesArithmeticException, the newNumberFormatExceptionis not caught locally and propagates up to the outercatchblock instead. catch (NumberFormatException e)(outer): Finally catches the exception that originated inside the innercatchblock, 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
ifstatement to check the condition, andthrow new IllegalArgumentException("...")inside it. - Wrap the method call in
mainwith atry-catchblock. - Use
e.getMessage()inside thecatchblock to retrieve the message passed to the exception’s constructor.
▼ Solution & Explanation
Explanation:
throw new IllegalArgumentException("Access Denied: Under 18"): Manually creates and throws an exception object, immediately stopping normal execution ofvalidateAge().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 inmain, 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 specificFileNotFoundException) to thereadFilemethod signature. - Do not wrap the
FileReadercreation in atry-catchinsidereadFileitself. - The caller in
mainis the one responsible for wrapping the call in atry-catch.
▼ Solution & Explanation
Explanation:
public static void readFile(String path) throws IOException: Thethrowsclause declares that this method may produce a checked exception without handling it internally, forcing every caller to deal with it.new FileReader(path): ThrowsFileNotFoundException, a subclass ofIOException, when the given file does not exist at that path.readFile("nonexistent.txt")insidetry: SincereadFiledeclaresthrows IOException, the compiler requiresmainto either catch it or declare its ownthrowsclause.catch (IOException e): Catches the exception at the point where the caller decides how to respond, rather than insidereadFileitself.
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 athrows IOExceptionclause, sinceIOExceptionis a checked exception the compiler tracks.methodB()needs nothrowsclause at all, sinceNullPointerExceptionis unchecked and the compiler does not require it to be declared.- Try removing the
try-catcharound themethodA()call and see that it fails to compile, then try the same withmethodB()and see that it still compiles fine.
▼ Solution & Explanation
Explanation:
methodA() throws IOException:IOExceptionis a checked exception, so the compiler forces any caller to either catch it or declare it in their ownthrowsclause.methodB()with nothrowsclause:NullPointerExceptionextendsRuntimeException, making it unchecked, so the compiler never requires it to be caught or declared.- First
try-catcharoundmethodA(): Mandatory here. Without it, the code simply would not compile because of the checkedthrows IOExceptiondeclaration. - Second
try-catcharoundmethodB(): Optional from the compiler’s perspective, sincemethodB()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
Exceptiondirectly, and pass the message through to the parent constructor withsuper(message). - Since it extends
Exceptionrather thanRuntimeException, thewithdraw()method must declarethrows InsufficientFundsException. - Compare
amountagainst the balance before subtracting, and throw before making any changes to the account state.
▼ Solution & Explanation
Explanation:
class InsufficientFundsException extends Exception: Creates a custom checked exception, meaning any method that can throw it must either catch it or declare it withthrows.super(message): Forwards the custom message to theExceptionparent class, so it becomes available later throughgetMessage().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 of500.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
RuntimeExceptioninstead ofException, 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.*"withmatches()to check for at least one digit.
▼ Solution & Explanation
Explanation:
class WeakPasswordException extends RuntimeException: Makes this an unchecked exception, sovalidatePassword()does not need athrowsdeclaration.password.matches(".*\\d.*"): Returnstrueif 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
SQLExceptionis checked, the method that catches and re-throws it needs its ownthrows SQLExceptiondeclaration. - Print the log message first inside the
catchblock. - Use
throw e;to re-throw the exact same exception object, rather than constructing a new one.
▼ Solution & Explanation
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 insidequeryDatabase(), then"Main caught: ..."prints once the same exception reaches the outercatchblock inmain.
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
catchparameter separated by|:catch (ParseException | IOException e). - Inside the block,
eis 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 aParseException.
▼ Solution & Explanation
Explanation:
throws ParseException, IOException: Declares thatprocessInput()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 identicalcatchblocks.format.parse(dateStr): ThrowsParseExceptionbecause"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 thecatchparameter 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
AutoCloseableand override its single abstract method,close(). - Declare the resource inside the parentheses of the
trystatement:try (MockDatabaseConnection connection = new MockDatabaseConnection()). - No
catchorfinallyblock is required. The resource still closes automatically once thetryblock ends.
▼ Solution & Explanation
Explanation:
implements AutoCloseable: Marks the class as a valid resource type that can be declared inside atry-with-resourcesstatement.try (MockDatabaseConnection connection = new MockDatabaseConnection()): Creates the resource as part of thetrystatement itself, rather than before it.close(): Called automatically by the JVM once thetryblock finishes, whether it completes normally or exits due to an exception.- No
finallyblock: Not needed here, sincetry-with-resourcesguarantees 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
Explanation:
- Broader exception (
Exception): Fails to compile. SinceParent.processData()only promisesIOException, code calling it through aParentreference is never prepared for a wider checked exception. - Narrower exception (
FileNotFoundException): Compiles successfully, sinceFileNotFoundExceptionis a subclass ofIOExceptionand still fits within the parent’s original contract. - Unchecked exception: Always compiles, regardless of the parent’s
throwsclause, 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 isParent.
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
DataAccessExceptiona constructor that accepts both a message and aThrowablecause, and forwards both tosuper(message, cause). - Inside the
catchblock 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
Explanation:
super(message, cause): Passes both the new, higher-level message and the original exception to the built-inThrowableconstructor that supports chaining.throw new DataAccessException("Fetch failed", e): Wraps the low-levelIOExceptioninside a business-level exception, replacing it in the thrown type while keeping a reference to it.e.getCause(): Retrieves the originalIOExceptionfrom inside theDataAccessException, 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()andclose()throw aRuntimeExceptionwith different messages. - The exception thrown by the
tryblock body becomes the primary exception thatcatchreceives. - Call
e.getSuppressed()on the caught exception. It returns an array of any exceptions that were suppressed during resource cleanup.
▼ Solution & Explanation
Explanation:
resource.use(): Throws first, while thetryblock 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 theclose()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-catchwrapped aroundworker.start()will not catch exceptions thrown inside the thread’s own run logic, sincestart()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()inmainso the program waits for the background thread to finish before continuing.
▼ Solution & Explanation
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 toSystem.err.worker.join(): Pauses the main thread until the worker thread finishes, ensuring the crash log prints before"Main thread continues normally".- Why
try-catchwould 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 surroundingtry-catchinmaincan 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. StackOverflowErrorextendsError, notException, so thecatchblock needs to targetErroror the common parentThrowableinstead.
▼ Solution & Explanation
Explanation:
recurse()calling itself with no base case: Keeps adding frames to the call stack until the JVM runs out of stack space and throwsStackOverflowError.- Why
catch (Exception e)fails:Throwablehas two direct subclasses,ExceptionandError, as separate branches. SinceStackOverflowErrordescends fromError, it is not anExceptionand slips right past acatch (Exception e)block. catch (Error e): Successfully intercepts the crash, sinceStackOverflowErroris a direct subclass ofError.- Why catching Errors is generally discouraged: An
ErrorlikeOutOfMemoryErrororStackOverflowErrorusually 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.

Leave a Reply