Java Exception Handling & Debugging
Understand Java exception hierarchies. Learn throw/throws declaration, checked vs unchecked exceptions, try-with-resources, and custom exceptions.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is the difference between checked and unchecked exceptions in Java?
| Type | Checked at | Must be declared/caught? | Examples |
|---|---|---|---|
| Checked exception | Compile time | Yes - or method must declare 'throws' | IOException, SQLException |
| Unchecked exception | Runtime only | No - compiler doesn't enforce handling | NullPointerException, ArithmeticException |
import java.io.*;
// Checked exception - compiler FORCES you to handle or declare it
public void readFile() throws IOException { // must declare
FileReader file = new FileReader('data.txt');
}
// Or handle it directly
public void readFileSafe() {
try {
FileReader file = new FileReader('data.txt');
} catch (IOException e) {
System.out.println('File error: ' + e.getMessage());
}
}
// Unchecked exception - compiler does NOT force handling
public void divide(int a, int b) {
int result = a / b; // no try-catch or throws required
}
// If b is 0, throws ArithmeticException at RUNTIME, not caught at compile time
// Class hierarchy:
// Throwable
// -> Exception (checked, unless it extends RuntimeException)
// -> RuntimeException (unchecked)
// -> Error (unchecked, serious JVM-level problems, e.g. OutOfMemoryError)
Q2. How does try-catch-finally work in Java, and when does finally NOT execute?
public int divide(int a, int b) {
try {
return a / b;
} catch (ArithmeticException e) {
System.out.println('Caught: ' + e.getMessage());
return -1;
} finally {
System.out.println('finally always runs'); // runs even with a return above
}
}
divide(10, 2); // prints 'finally always runs', returns 5
divide(10, 0); // prints 'Caught: / by zero', 'finally always runs', returns -1
// finally runs even when try/catch has a return statement
public int example() {
try {
return 1;
} finally {
System.out.println('finally runs before the return completes');
}
}
// Rare cases where finally does NOT execute:
// 1. JVM crashes or is killed (System.exit(), power failure)
public void withExit() {
try {
System.exit(0); // finally will NOT run
} finally {
System.out.println('This never prints');
}
}
// 2. Infinite loop or deadlock inside try block - never reaches finally
// 3. The thread executing try is forcibly killed (Thread.stop() - deprecated)
Q3. How do you create and throw a custom exception in Java?
Custom exceptions are created by extending Exception (for checked) or RuntimeException (for unchecked), letting you represent domain-specific error conditions clearly.
// Custom CHECKED exception - must be declared or caught
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(
'Cannot withdraw ' + amount + ', balance is only ' + balance);
}
balance -= amount;
}
}
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount(100);
try {
account.withdraw(150);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
}
}
}
// Custom UNCHECKED exception - no 'throws' declaration required
class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message);
}
}
public void setAge(int age) {
if (age < 0) {
throw new InvalidAgeException('Age cannot be negative: ' + age);
}
}
// Adding custom fields to an exception
class InsufficientFundsException2 extends Exception {
private final double shortfall;
public InsufficientFundsException2(double shortfall) {
super('Short by ' + shortfall);
this.shortfall = shortfall;
}
public double getShortfall() { return shortfall; }
}
Q4. What is try-with-resources and why is it preferred over manual resource closing?
try-with-resources automatically closes any resource that implements AutoCloseable when the try block finishes, even if an exception occurs - eliminating the need for manual close() calls in finally.
// OLD WAY - manual resource management, verbose and error-prone
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader('data.txt'));
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println('Error: ' + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close(); // must remember this, and it can ALSO throw
} catch (IOException e) {
e.printStackTrace();
}
}
}
// MODERN WAY - try-with-resources (Java 7+)
try (BufferedReader reader2 = new BufferedReader(new FileReader('data.txt'))) {
System.out.println(reader2.readLine());
} catch (IOException e) {
System.out.println('Error: ' + e.getMessage());
}
// reader2.close() is called AUTOMATICALLY, even if an exception occurs
// Multiple resources - closed in REVERSE order of declaration
try (
FileInputStream in = new FileInputStream('input.txt');
FileOutputStream out = new FileOutputStream('output.txt')
) {
// use both streams
} // out closed first, then in
// Custom class using try-with-resources - must implement AutoCloseable
class MyResource implements AutoCloseable {
public void use() { System.out.println('Using resource'); }
@Override
public void close() { System.out.println('Resource closed'); }
}
try (MyResource resource = new MyResource()) {
resource.use();
} // close() called automatically
Q5. What is the difference between throw and throws in Java?
| Keyword | Used for | Location | Number allowed |
|---|---|---|---|
| throw | Actually throwing an exception instance | Inside a method body | One exception object per throw statement |
| throws | Declaring that a method MIGHT throw an exception | In the method signature | Multiple exceptions, comma-separated |
import java.io.IOException;
import java.sql.SQLException;
// 'throws' - declares possible exceptions in the method signature
public void processFile(String path) throws IOException, SQLException {
if (path == null) {
// 'throw' - actually creates and throws an exception instance
throw new IllegalArgumentException('Path cannot be null');
}
// ... code that might throw IOException or SQLException ...
}
// Caller must handle or re-declare the checked exceptions
public void caller() {
try {
processFile('data.txt');
} catch (IOException | SQLException e) { // multi-catch (Java 7+)
System.out.println('Error: ' + e.getMessage());
}
}
// 'throw' can also re-throw a caught exception
public void handleAndRethrow() throws IOException {
try {
processFile(null);
} catch (IOException e) {
System.out.println('Logging error before rethrow');
throw e; // re-throws the SAME exception object
} catch (SQLException e) {
// handled, not rethrown
}
}
// Memory tip: 'throw' is a VERB (the action of throwing),
// 'throws' is a DECLARATION (a warning label on the method)
Q6. What is exception chaining in Java and what does getCause() do?
Exception chaining preserves the original exception when wrapping it in a new one, using getCause() to trace back through the full chain - critical for debugging layered application errors.
class ServiceException extends Exception {
public ServiceException(String message, Throwable cause) {
super(message, cause); // links to the original cause
}
}
public void fetchUserData() throws ServiceException {
try {
// Simulating a lower-level failure
throw new java.sql.SQLException('Connection timeout');
} catch (java.sql.SQLException e) {
// Wrap in a higher-level, more meaningful exception, preserving the cause
throw new ServiceException('Failed to fetch user data', e);
}
}
public void caller() {
try {
fetchUserData();
} catch (ServiceException e) {
System.out.println('Error: ' + e.getMessage()); // Failed to fetch user data
System.out.println('Caused by: ' + e.getCause()); // java.sql.SQLException: Connection timeout
Throwable cause = e.getCause();
while (cause != null) {
System.out.println(' Chain: ' + cause);
cause = cause.getCause(); // walk the full chain if deeply nested
}
}
}
// Printing the full chain in a stack trace automatically
try {
fetchUserData();
} catch (ServiceException e) {
e.printStackTrace(); // shows 'Caused by: java.sql.SQLException...' automatically
}
// Without chaining (losing context) - AVOID this pattern:
catch (java.sql.SQLException e) {
throw new ServiceException('Failed to fetch user data', null); // cause lost!
}
Q7. What happens if an exception is thrown inside a finally block?
An exception thrown in finally SUPPRESSES any exception from the try or catch block - only the finally block's exception propagates, and the original is lost unless explicitly captured.
public void problematic() {
try {
throw new RuntimeException('Original exception from try');
} finally {
throw new RuntimeException('Exception from finally'); // this WINS
}
}
public static void main(String[] args) {
try {
new Main().problematic();
} catch (RuntimeException e) {
System.out.println(e.getMessage()); // 'Exception from finally' - original is LOST
}
}
// This is considered a BAD PRACTICE because it silently discards
// the original error information, making debugging harder
// SAFER pattern - avoid throwing new exceptions in finally;
// only use finally for cleanup that itself shouldn't fail
public void safer() {
Connection conn = null;
try {
conn = openConnection();
performWork(conn);
} finally {
if (conn != null) {
try {
conn.close(); // wrap risky cleanup in its own try-catch
} catch (Exception closeException) {
System.out.println('Failed to close, but not masking original error');
}
}
}
}
// try-with-resources handles this better automatically via 'suppressed exceptions'
// - if both the try block AND close() throw, the close() exception is
// added to the ORIGINAL exception's getSuppressed() array, not lost
try (MyResource r = new MyResource()) {
throw new RuntimeException('try block error');
}
// catch (RuntimeException e) { e.getSuppressed(); } would show close() errors too
Q8. What is a StackOverflowError vs an OutOfMemoryError in Java?
| Error | Cause | Common trigger |
|---|---|---|
| StackOverflowError | Call stack exceeds its maximum size | Infinite or excessively deep recursion |
| OutOfMemoryError | Heap (or other memory area) exhausted | Memory leaks, loading huge datasets, too many objects retained |
// StackOverflowError example
public void recurse() {
recurse(); // no base case - each call adds a new stack frame
}
// recurse(); // java.lang.StackOverflowError
// Fixed with a proper base case
public int factorial(int n) {
if (n <= 1) return 1; // base case stops the recursion
return n * factorial(n - 1);
}
// OutOfMemoryError example - heap space exhausted
import java.util.*;
public void memoryLeak() {
List<int[]> list = new ArrayList<>();
while (true) {
list.add(new int[1000000]); // keeps allocating, never released
}
}
// java.lang.OutOfMemoryError: Java heap space
// Both extend Error (not Exception) since they represent serious,
// often unrecoverable JVM-level problems that applications generally
// should NOT try to catch and continue from
// Both ARE technically catchable, but doing so is usually a bad idea:
try {
recurse();
} catch (StackOverflowError e) {
System.out.println('Caught, but the program state may be unstable now');
}
// Increasing JVM memory limits (doesn't fix underlying leaks, just delays them)
// java -Xmx512m -Xss1m MyApp (-Xmx: heap size, -Xss: thread stack size)
Q9. What are best practices for logging and debugging exceptions in production Java applications?
| Practice | Why it matters |
|---|---|
| Use a logging framework (SLF4J, Log4j), not System.out | Configurable levels, output destinations, and performance |
| Log the full stack trace | Root cause is often deep in the chain, not just the top message |
| Never swallow exceptions silently | Empty catch blocks hide real problems |
| Include context in log messages | User ID, request ID, operation name help correlate issues |
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class OrderService {
private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
public void processOrder(String orderId) {
try {
// ... processing logic ...
} catch (Exception e) {
// GOOD: logs message, context, AND full stack trace
logger.error('Failed to process order {}: {}', orderId, e.getMessage(), e);
throw new RuntimeException('Order processing failed', e); // rethrow, don't swallow
}
}
}
// BAD PRACTICE - silently swallowing exceptions
public void badExample() {
try {
riskyOperation();
} catch (Exception e) {
// empty catch block - error disappears completely, very hard to debug later
}
}
// BAD PRACTICE - printStackTrace() only, no structured logging
catch (Exception e) {
e.printStackTrace(); // goes to console only, lost in production, no log levels
}
// GOOD - catching specific exceptions, not blanket 'catch (Exception e)' everywhere
try {
parseInput(data);
} catch (NumberFormatException e) {
logger.warn('Invalid number format in input: {}', data);
} catch (IllegalArgumentException e) {
logger.warn('Invalid argument: {}', e.getMessage());
}
// Blanket catches make it hard to know what specifically went wrong
Q10. Can overriding methods change the checked exceptions declared by the parent method?
Java restricts what checked exceptions an overriding method can declare - it can throw the SAME, FEWER, or MORE SPECIFIC (subclass) checked exceptions than the parent, but never new or broader ones.
import java.io.IOException;
import java.io.FileNotFoundException;
class Parent {
public void readData() throws IOException {
// ...
}
}
class ChildOk extends Parent {
// OK - FileNotFoundException is a SUBCLASS of IOException (more specific)
@Override
public void readData() throws FileNotFoundException {
// ...
}
}
class ChildAlsoOk extends Parent {
// OK - declaring NO checked exception at all is always allowed
@Override
public void readData() {
// ...
}
}
// class ChildBad extends Parent {
// // COMPILE ERROR - SQLException is NOT a subclass of IOException,
// // this would be a BROADER contract than the parent allows
// @Override
// public void readData() throws java.sql.SQLException {
// }
// }
// Unchecked exceptions have NO such restriction - a method can
// throw any unchecked (RuntimeException) exception when overriding,
// regardless of what the parent declares
class Parent2 {
public void process() { } // declares nothing
}
class Child2 extends Parent2 {
@Override
public void process() {
throw new IllegalStateException('allowed - unchecked exceptions are unrestricted');
}
}
// Why this rule exists: it preserves Liskov substitution -
// code calling Parent.readData() and only catching IOException
// must still work correctly when a Child instance is used instead
Java Exception Handling & Debugging
Understand Java exception hierarchies. Learn throw/throws declaration, checked vs unchecked exceptions, try-with-resources, and custom exceptions.
What is the difference between checked and unchecked exceptions in Java?
TypeChecked atMust be declared/caught?ExamplesChecked exceptionCompile timeYes - or method must declare 'throw...
How does try-catch-finally work in Java, and when does finally NOT execute?
public int divide(int a, int b) { try { return a / b; } catch (ArithmeticException e) {...
How do you create and throw a custom exception in Java?
Custom exceptions are created by extending Exception (for checked) or RuntimeException (for unchecked), lettin...
What is try-with-resources and why is it preferred over manual resource closing?
try-with-resources automatically closes any resource that implements AutoCloseable when the try block finishes...
What is the difference between throw and throws in Java?
KeywordUsed forLocationNumber allowedthrowActually throwing an exception instanceInside a method bodyOne excep...
What is exception chaining in Java and what does getCause() do?
Exception chaining preserves the original exception when wrapping it in a new one, using getCause() to trace b...
What happens if an exception is thrown inside a finally block?
An exception thrown in finally SUPPRESSES any exception from the try or catch block - only the finally block's...
What is a StackOverflowError vs an OutOfMemoryError in Java?
ErrorCauseCommon triggerStackOverflowErrorCall stack exceeds its maximum sizeInfinite or excessively deep recu...
What are best practices for logging and debugging exceptions in production Java applications?
PracticeWhy it mattersUse a logging framework (SLF4J, Log4j), not System.outConfigurable levels, output destin...
Can overriding methods change the checked exceptions declared by the parent method?
Java restricts what checked exceptions an overriding method can declare - it can throw the SAME, FEWER, or MOR...