15 questions found
What is the basic syntax and control flow of a try-catch-finally block, and what determines which catch block (if multiple exist) handles a thrown exception?
Beginner
A try block contains code that might throw an exception; one or more catch blocks (each specifying a particular exception type) follow, with the JVM checking them in declared order and executing the FIRST catch block whose declared type matches (via instanceof-style checking) the actual thrown exception's type -- meaning catch blocks should generally be ordered from most specific to most general (a more general catch block declared before a more specific one would make the specific one unreachable, which the compiler actually flags as an error); an optional finally block always executes afterward regardless of what happened.
try {
riskyOperation();
} catch (FileNotFoundException e) { // more specific, checked first
handleMissingFile(e);
} catch (IOException e) { // more general, only reached if NOT a FileNotFoundException
handleGeneralIOError(e);
} finally {
cleanup();
}
Real-world example
A file-processing method with a specific catch (FileNotFoundException e) block preceding a more general catch (IOException e) block correctly routes a missing-file scenario to specialized handling (suggesting the correct file path to the user) while still catching any other I/O failure through the general handler, relying on the JVM's declared-order matching.
Common follow-ups: What compiler error occurs if you accidentally place a more general catch block before a more specific one?;How would you determine the exact runtime type of a caught exception if the catch block declares a general supertype?
Error Handling;Java Fundamentals: Syntax
Data Types & Operators
How do you create and throw a custom exception class, and what constructors should a well-designed custom exception typically provide?
Intermediate
A custom exception extends either Exception (checked) or RuntimeException (unchecked) as appropriate, and by convention should provide at least the standard four constructors mirroring Throwable's own (no-arg, message-only, message-plus-cause, and cause-only), ensuring the custom exception integrates smoothly with common exception-handling patterns (like wrapping-and-rethrowing with a cause) that calling code and libraries typically expect to be available on any well-behaved exception class.
public class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException() { super(); }
public InsufficientFundsException(String message) { super(message); }
public InsufficientFundsException(String message, Throwable cause) { super(message, cause); }
public InsufficientFundsException(Throwable cause) { super(cause); }
}
throw new InsufficientFundsException("Account balance too low for withdrawal of $500");
Real-world example
A banking application's InsufficientFundsException provides all four standard constructors, letting it be thrown with just a descriptive message in most cases, but also usable with a wrapped cause when the insufficient-funds determination itself resulted from a downstream service call failure.
Common follow-ups: Why is it considered good practice to provide all four standard constructors even if only one is currently used?;What naming convention (ending in 'Exception') helps make custom exception classes immediately recognizable in a codebase?
OOP & Classes;Error Handling
How does exception handling interact with generics and type erasure, specifically regarding why you cannot catch a generic type parameter as an exception type (catch (T e))?
Advanced
Due to type erasure, a generic type parameter T has no runtime representation the JVM could use to determine whether a thrown exception actually matches T at a specific catch site (the compiled bytecode has no way to check 'is this exception an instance of whatever T happens to be for this particular invocation'), so Java simply disallows catch (T e) entirely as a compile-time error -- similarly, you cannot create a generic exception class (class MyException<T> extends Exception is illegal) since each distinct throwable type needs a genuine, fixed runtime identity for the exception-matching mechanism to function correctly, another manifestation of the same type erasure limitation that also prevents generic array creation.
// This does NOT compile -- illegal to catch a type parameter
public <T extends Exception> void doSomething() {
try {
riskyOperation();
} catch (T e) { // COMPILE ERROR: cannot catch a type variable
// ...
}
}
// Also illegal: a generic exception class
// class MyException<T> extends Exception { } // COMPILE ERROR
Real-world example
A generic retry-wrapper utility attempting to catch a type-parameterized exception type (to apply generic retry logic for a caller-specified exception class) hits a compile error and instead has to accept a Class<T> token parameter combined with an isInstance() check at runtime, working around type erasure's fundamental limitation here.
Common follow-ups: How would you work around this limitation using a Class<T> token parameter and isInstance() checking instead?;What's the connection between this restriction and the earlier-discussed inability to create generic arrays?
Generics;Class Loading & Bytecode Verification
What is the purpose of the addSuppressed() mechanism and getSuppressed() method on Throwable, and in what specific scenario does the JVM automatically populate suppressed exceptions?
Intermediate
Suppressed exceptions handle the scenario where a try-with-resources block's body throws an exception, AND the automatic close() call on one or more resources ALSO throws an exception during cleanup -- rather than losing one of these two genuinely important pieces of information, the JVM propagates the original (primary) exception from the try block's body while automatically attaching the close()-triggered exception(s) as suppressed exceptions on it, accessible via getSuppressed(), ensuring neither failure is silently discarded even though only one exception can be the 'main' one propagating up the call stack.
try (AutoCloseable resource = () -> { throw new IOException("close failed"); }) {
throw new RuntimeException("operation failed"); // this becomes the PRIMARY exception
}
// Catching this: e.getMessage() == "operation failed"
// e.getSuppressed()[0].getMessage() == "close failed" -- NOT lost, just attached as suppressed
Real-world example
A database transaction wrapped in try-with-resources throws a business-logic exception while its connection's close() method also fails due to a broken network connection, and the resulting stack trace shows both failures clearly (the primary business exception plus the suppressed connection-close failure), giving a complete diagnostic picture instead of silently losing one of the two failures.
Common follow-ups: How would you programmatically inspect and log all suppressed exceptions attached to a caught exception?;What's the difference conceptually between a suppressed exception and a chained cause exception?
I/O & NIO;Logging in Java (java.util.logging
SLF4J
Log4j)
How would you design and implement a global, centralized exception-handling strategy for a Java application (such as a top-level handler in main(), or an uncaught exception handler for background threads), ensuring no exception silently crashes the application without being logged?
Advanced
Thread.setDefaultUncaughtExceptionHandler() (JVM-wide) or Thread.setUncaughtExceptionHandler() (per-thread) registers a callback invoked whenever an exception propagates uncaught out of a thread's run() method (which would otherwise silently terminate that thread with only a default stack-trace print to stderr, easy to miss in production, especially for a background worker thread whose termination might not be immediately obvious) -- a well-designed global handler ensures every uncaught exception is properly logged (with full context) through the application's actual logging infrastructure, potentially triggers alerting for critical failures, and for genuinely unrecoverable situations, can trigger a controlled, graceful application shutdown rather than leaving the application in a partially-broken state with a silently-dead thread.
public class Application {
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
logger.error("Uncaught exception in thread " + thread.getName(), exception);
// optionally trigger alerting, or a controlled shutdown for critical failures
});
// ... application startup
}
}
Real-world example
A background worker thread pool's tasks were silently failing (an unhandled exception simply terminated the specific worker thread, with only a barely-noticed stderr print, while the rest of the application continued running seemingly normally) until a global UncaughtExceptionHandler was registered, properly logging and alerting on every such failure through the team's actual monitoring infrastructure instead of relying on someone happening to notice missing stderr output.
Common follow-ups: Why doesn't a try-catch in the main() method alone catch exceptions thrown on separate background threads?;How does this uncaught exception handler interact with exceptions occurring inside an ExecutorService-managed thread pool's tasks?
Global Exception Handling & Middleware;Logging in Java (java.util.logging
SLF4J
Log4j)