| Throwable | Root class of all errors and exceptions in Java |
| Checked Exception | Must be declared or caught; compiler enforces it (e.g., IOException) |
| Unchecked Exception | Subclass of RuntimeException; not enforced by compiler (e.g., NullPointerException) |
| Error | Serious JVM problems not meant to be caught (e.g., OutOfMemoryError) |
| try-with-resources | Java 7+ auto-closes AutoCloseable resources after try block |
| multi-catch | Java 7+ catch (A | B e) syntax to handle multiple exception types in one block |
| Exception chaining | Wrapping one exception inside another using getCause()/initCause() |
| Suppressed exceptions | Exceptions thrown in close() suppressed when try-with-resources closes resources |
| finally | Always executes after try/catch unless JVM exits (System.exit) |
| Sneaky throws | Trick to throw checked exceptions without declaring them (via generics erasure) |
StackOverflowError
ExceptionInInitializerError
VirtualMachineError
SQLException
ClassNotFoundException
ParseException
ArrayIndexOutOfBoundsException
ClassCastException
IllegalArgumentException
Java Exception Handling Interview Questions & Answers
Q1. What is an exception in Java?
A: An exception in Java is an abnormal event that disrupts the normal flow of program execution. It is an object that wraps an error event and carries information about the error including its type, state of the program when the error occurred, and a message describing the error. Java provides a robust mechanism to handle exceptions using try, catch, finally, throw, and throws keywords, allowing programs to recover from errors gracefully rather than crashing abruptly.
Q2. What is the Throwable class in Java?
A: java.lang.Throwable is the root class of the entire Java exception hierarchy. Only objects that are instances of Throwable (or its subclasses) can be thrown by the JVM or by the throw statement, and only Throwable objects can be caught by the catch clause. Throwable has two direct subclasses: Error and Exception. It provides key methods such as getMessage(), getCause(), getStackTrace(), printStackTrace(), initCause(), and addSuppressed().
Q3. What is the difference between Error and Exception in Java?
A: Both Error and Exception extend Throwable, but they serve different purposes:
| Error | Exception |
|---|---|
| Serious JVM-level problems | Application-level problems |
| Not meant to be caught or handled | Designed to be caught and handled |
| Indicates unrecoverable conditions | Indicates recoverable conditions |
| Examples: OutOfMemoryError, StackOverflowError | Examples: IOException, NullPointerException |
| Unchecked (compiler doesn't enforce) | Can be checked or unchecked |
Q4. What is the difference between checked and unchecked exceptions?
A: Checked exceptions are exceptions that the compiler forces you to either handle (with try-catch) or declare (with throws). They represent recoverable conditions outside the program's control. Unchecked exceptions (subclasses of RuntimeException) are not enforced by the compiler and usually indicate programming bugs.
| Aspect | Checked Exception | Unchecked Exception |
|---|---|---|
| Superclass | Exception (not RuntimeException) | RuntimeException |
| Compiler enforcement | Yes — must catch or declare | No |
| Represents | External/recoverable conditions | Programming bugs/logic errors |
| Examples | IOException, SQLException, ClassNotFoundException | NullPointerException, ArrayIndexOutOfBoundsException |
| Best practice | Handle or propagate with meaningful context | Fix the bug; don't swallow silently |
Q5. Name common checked exceptions in Java.
A: Common checked exceptions include: java.io.IOException (I/O operations fail), java.sql.SQLException (database access errors), java.lang.ClassNotFoundException (class not found at runtime), java.text.ParseException (string parsing fails), java.io.FileNotFoundException (file does not exist — subclass of IOException), java.net.MalformedURLException (malformed URL), java.lang.InterruptedException (thread interrupted), and java.lang.reflect.InvocationTargetException (reflection invocation fails).
Q6. Name common unchecked exceptions in Java.
A: Common unchecked (RuntimeException subclasses) include: NullPointerException (null reference dereference), ArrayIndexOutOfBoundsException (invalid array index), ClassCastException (invalid type cast), IllegalArgumentException (illegal method argument), IllegalStateException (illegal state for method call), NumberFormatException (bad string-to-number conversion), ArithmeticException (e.g., division by zero), UnsupportedOperationException (operation not supported), and ConcurrentModificationException (collection modified during iteration).
Q7. What is StackOverflowError and when does it occur?
A: StackOverflowError is a subclass of VirtualMachineError (and thus Error) that is thrown when the JVM's thread stack runs out of space. The most common cause is infinite or excessively deep recursion — each method call pushes a new stack frame, and when there is no more stack space, the JVM throws StackOverflowError. It is an unchecked error and should not be caught; instead, fix the recursive logic.
// Causes StackOverflowError
public void infinite() {
infinite(); // no base case
}
// Fix: add a base case
public void recursive(int n) {
if (n == 0) return; // base case
recursive(n - 1);
}
Q8. What is OutOfMemoryError and how does it differ from StackOverflowError?
A: OutOfMemoryError is thrown when the JVM cannot allocate an object because the heap is exhausted and the garbage collector cannot free enough memory. StackOverflowError occurs when the thread stack space is exhausted, typically from deep recursion. OutOfMemoryError affects heap memory (objects), while StackOverflowError affects stack memory (method call frames). Both are Errors and should not be caught in normal application code.
Q9. Explain try-catch-finally execution order.
A: The execution order is: (1) The try block executes. (2) If an exception is thrown, control transfers to the matching catch block. (3) If no exception is thrown, catch blocks are skipped. (4) The finally block always executes — whether or not an exception was thrown or caught. If the try block has a return statement, the finally block still executes before the method actually returns. The only exception to "finally always runs" is if System.exit() is called or the JVM crashes.
public int example() {
try {
System.out.println("try");
return 1; // finally still runs before returning
} catch (Exception e) {
System.out.println("catch");
return 2;
} finally {
System.out.println("finally"); // always executes
// returning here overrides the try/catch return value!
}
}
// Output: try, finally (returns 1 unless finally has its own return)
Q10. Does finally always execute? What are the exceptions?
A: The finally block executes in almost all cases, including when an exception is thrown and not caught, when a return statement is in the try or catch block, and when a break or continue is used. The cases where finally does NOT execute are: (1) System.exit() is called — this terminates the JVM immediately. (2) The JVM crashes or is killed externally (e.g., kill -9). (3) An infinite loop or deadlock in the try block prevents it from completing. (4) The thread executing the try block is killed abruptly.
Q11. What is try-with-resources and when was it introduced?
A: Try-with-resources was introduced in Java 7. It automatically closes resources that implement the java.lang.AutoCloseable (or java.io.Closeable) interface when the try block exits, whether normally or via an exception. This eliminates the need for boilerplate finally blocks to close resources and prevents resource leaks.
// Before Java 7 — verbose and error-prone
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("file.txt"));
return br.readLine();
} finally {
if (br != null) br.close(); // must not forget!
}
// Java 7+ try-with-resources — clean and safe
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
return br.readLine();
} // br.close() called automatically
// Java 9+: effectively-final variables can be used directly
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
try (br) {
return br.readLine();
}
Q12. What is the AutoCloseable interface?
A: java.lang.AutoCloseable is a functional interface introduced in Java 7 with a single method void close() throws Exception. Any class that implements AutoCloseable can be used in a try-with-resources statement. The java.io.Closeable interface extends AutoCloseable but narrows the close() method to only throw IOException. Resources in try-with-resources are closed in reverse order of declaration.
Q13. What are suppressed exceptions in try-with-resources?
A: When using try-with-resources, if both the try block and the close() method throw exceptions, the exception from close() is suppressed (not lost) to preserve the primary exception. Suppressed exceptions are attached to the primary exception and can be retrieved via Throwable.getSuppressed(). This is a major improvement over traditional try-finally where the finally block's exception would completely replace the try block's exception, losing the original error information.
class MyResource implements AutoCloseable {
public void use() throws Exception { throw new Exception("Primary"); }
public void close() throws Exception { throw new Exception("Suppressed"); }
}
try (MyResource r = new MyResource()) {
r.use(); // throws "Primary"
} // close() throws "Suppressed" — gets added as suppressed
// Catching:
catch (Exception e) {
System.out.println(e.getMessage()); // Primary
for (Throwable s : e.getSuppressed()) {
System.out.println(s.getMessage()); // Suppressed
}
}
Q14. What is multi-catch syntax in Java?
A: Multi-catch, introduced in Java 7, allows a single catch block to handle multiple exception types using the pipe (|) operator. This reduces code duplication when multiple exceptions require identical handling. The caught variable is implicitly final in a multi-catch block, meaning it cannot be reassigned. The exception types in a multi-catch must not have a supertype-subtype relationship (compiler error otherwise).
// Before Java 7 — repetitive
try {
// risky code
} catch (IOException e) {
log(e);
} catch (SQLException e) {
log(e);
}
// Java 7+ multi-catch — concise
try {
// risky code
} catch (IOException | SQLException e) {
log(e); // e is implicitly final
}
// Compiler error — IllegalArgumentException is a supertype of NumberFormatException
// catch (IllegalArgumentException | NumberFormatException e) { } // ERROR
Q15. What is exception chaining?
A: Exception chaining (also called exception wrapping) is the practice of catching one exception and throwing a new exception that includes the original as its cause. This preserves the full context of what went wrong at each layer of the application. The cause can be set via constructor (new Exception("msg", cause)) or via initCause(cause). The cause is retrieved with getCause(). Exception chaining is crucial for debugging because it maintains the original stack trace alongside the new exception's stack trace.
// Service layer catches low-level exception and wraps it
public User loadUser(int id) throws ServiceException {
try {
return userDao.findById(id);
} catch (SQLException e) {
// Chain: preserve original cause
throw new ServiceException("Failed to load user: " + id, e);
}
}
// Retrieving the cause
try {
loadUser(42);
} catch (ServiceException e) {
System.out.println(e.getMessage());
System.out.println("Caused by: " + e.getCause()); // original SQLException
}
Q16. What is the difference between throw and throws in Java?
A: throw is a statement used inside a method body to explicitly throw an exception object. It transfers control to the nearest matching catch block or propagates up the call stack. throws is a keyword used in a method or constructor signature to declare that the method may throw one or more checked exceptions, warning callers to handle them. throw is used with an instance; throws is used with class names.
Q17. What are custom exceptions and what are best practices for creating them?
A: Custom exceptions are user-defined exception classes that extend Exception (checked) or RuntimeException (unchecked). Best practices: (1) Prefer unchecked custom exceptions for programming errors; use checked for recoverable, expected failure conditions. (2) Provide at least four constructors: no-arg, message-only, message+cause, and cause-only — mirroring the standard Exception constructors. (3) Give the class a meaningful name ending in "Exception". (4) Add domain-specific fields for error codes or context. (5) Include javadoc. (6) Do not create an exception hierarchy deeper than 2-3 levels.
public class OrderProcessingException extends RuntimeException {
private final String orderId;
private final int errorCode;
public OrderProcessingException(String message) {
super(message);
this.orderId = null;
this.errorCode = -1;
}
public OrderProcessingException(String message, Throwable cause) {
super(message, cause);
this.orderId = null;
this.errorCode = -1;
}
public OrderProcessingException(String orderId, int errorCode, String message, Throwable cause) {
super(message, cause);
this.orderId = orderId;
this.errorCode = errorCode;
}
public String getOrderId() { return orderId; }
public int getErrorCode() { return errorCode; }
}
Q18. What is re-throwing an exception?
A: Re-throwing means catching an exception and then throwing it again (or a new exception wrapping it). This is useful when you want to log the exception or do partial handling before propagating it. In Java 7+, if you re-throw a caught exception whose type is more specific than the declared type, the compiler is smart enough (precise rethrow) to allow a narrower throws declaration. Example: catching Exception but rethrowing only the specific subtypes that were actually thrown.
Q19. What is the rule for method overriding and exceptions?
A: When overriding a method, the overriding method can: (1) throw the same checked exceptions as the parent. (2) throw fewer checked exceptions (or none at all). (3) throw narrower (subclass) checked exceptions. It CANNOT throw broader or new checked exceptions not declared by the parent. There is no restriction on unchecked exceptions — overriding methods can throw any RuntimeException regardless. This is the Liskov Substitution Principle applied to exceptions: callers of the parent's interface must not be surprised by new checked exceptions.
Q20. Can a constructor throw an exception?
A: Yes. Constructors can throw both checked and unchecked exceptions. If a constructor throws an exception, the object is not fully constructed and no reference to it escapes, so there is no partially-initialized object to worry about (from the caller's perspective). The object may have allocated resources before throwing; any such resources should be cleaned up inside the constructor. This is one reason try-with-resources cannot be used in constructors — you must use try-finally manually for constructor-level cleanup.
Q21. What is ExceptionInInitializerError?
A: ExceptionInInitializerError is thrown when an unexpected exception occurs during evaluation of a static initializer or the initializer for a static variable. It wraps the original exception, which can be retrieved via getCause(). Once a class fails to initialize this way, any subsequent attempt to use the class will throw NoClassDefFoundError. It is an Error (not Exception) and indicates a problem with the class's static setup.
class Config {
static int value = Integer.parseInt("not-a-number"); // NumberFormatException
// Causes ExceptionInInitializerError wrapping NumberFormatException
}
// Using Config anywhere after this throws NoClassDefFoundError
try {
new Config();
} catch (ExceptionInInitializerError e) {
System.out.println("Cause: " + e.getCause()); // NumberFormatException
}
Q22. What happens if an exception is thrown inside a static initializer?
A: If a checked or unchecked exception is thrown inside a static initializer block, the JVM wraps it in an ExceptionInInitializerError and throws it. The class is then marked as failed to initialize. Any future attempt to use the class (create instances, access static members) will result in a NoClassDefFoundError, not another ExceptionInInitializerError. The original cause is available via getCause() on the ExceptionInInitializerError.
Q23. What are sneaky throws in Java?
A: Sneaky throws is a technique that exploits Java's type erasure during generic type inference to throw a checked exception without declaring it in the method's throws clause. The JVM does not actually enforce checked-vs-unchecked at runtime — only the compiler does. By using an unchecked cast trick via generics, you can fool the compiler into treating a checked exception as unchecked. This is considered bad practice in production code but useful in certain framework scenarios (e.g., Lombok's @SneakyThrows).
// Sneaky throw implementation
@SuppressWarnings("unchecked")
public static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
throw (T) t; // unchecked cast — compiler infers T as RuntimeException
}
// Usage: throw IOException without declaring it
public void run() {
sneakyThrow(new IOException("Sneaky!")); // no throws declaration needed
}
// Lombok equivalent:
@SneakyThrows
public void run() throws IOException {
throw new IOException("Sneaky!");
}
Q24. How do you handle exceptions in lambda expressions?
A: Lambda expressions cannot throw checked exceptions unless the functional interface's abstract method declares them. To handle this: (1) Wrap the checked exception in a RuntimeException inside the lambda. (2) Create a custom functional interface that declares the checked exception. (3) Use a wrapper method that catches the checked exception and rethrows as unchecked. (4) Use @SneakyThrows (Lombok). Option 1 is the most common approach in standard Java.
// Problem: Files.readAllBytes throws IOException (checked)
List<String> files = List.of("a.txt", "b.txt");
// Option 1: wrap in RuntimeException
files.stream()
.map(f -> {
try {
return Files.readAllBytes(Path.of(f));
} catch (IOException e) {
throw new RuntimeException(e); // wrap
}
})
.collect(Collectors.toList());
// Option 2: custom functional interface
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
// Option 3: wrapper utility
public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> f) {
return t -> {
try { return f.apply(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
files.stream().map(wrap(f -> Files.readAllBytes(Path.of(f)))).collect(...);
Q25. What are effective exception handling patterns?
A: Key patterns: (1) Catch specific exceptions, not broad ones. (2) Never swallow exceptions silently (empty catch blocks are almost always wrong). (3) Log before or after throwing — not both — to avoid duplicate log entries. (4) Use exception chaining to preserve context. (5) Clean up resources in finally or use try-with-resources. (6) Fail fast — validate inputs early and throw meaningful exceptions. (7) Use unchecked exceptions for programming errors; checked for expected failure conditions. (8) Do not use exceptions for normal control flow (performance penalty). (9) Provide enough context in the message for debugging. (10) Document exceptions in Javadoc using @throws.
Q26. Why should you never swallow exceptions?
A: Swallowing an exception (catching it and doing nothing) hides bugs, makes debugging extremely difficult, and can leave the application in an inconsistent state. The root cause of a failure becomes invisible — hours are wasted wondering why the system is misbehaving when the real cause was silently discarded. At minimum, log the exception. If you genuinely cannot handle it, rethrow it (possibly wrapped) or let it propagate. The only acceptable silent catch is in specific cleanup code where you are already handling a primary exception.
Q27. What does "log before throw" mean?
A: "Log before throw" means you should log an exception at the point where you have the most context, before rethrowing it or wrapping it. However, avoid logging the same exception multiple times at different layers (log-and-rethrow at every layer), which creates duplicate log entries. The convention is: log the exception at the boundary closest to the user (or at the point where it's ultimately handled), not at every catch-and-rethrow point. Use exception chaining so that context from lower layers is preserved without re-logging.
Q28. What is NullPointerException and how can it be avoided?
A: NullPointerException (NPE) is thrown when you attempt to use a null reference: calling methods, accessing fields, indexing arrays, etc. In Java 14+, helpful NPE messages identify the exact null variable. Avoidance strategies: (1) Use Optional<T> for values that may be absent. (2) Validate method parameters with Objects.requireNonNull(). (3) Initialize fields in constructors. (4) Use @NonNull/@Nullable annotations with static analysis tools. (5) Prefer empty collections over null. (6) Use defensive null checks where necessary.
Q29. What is ArrayIndexOutOfBoundsException?
A: ArrayIndexOutOfBoundsException is thrown when code attempts to access an array element at an index that is negative or greater than or equal to the array's length. It is an unchecked exception (RuntimeException subclass) and a programming bug. Prevention: always validate indices before accessing, use array.length to bound loops, prefer List or other collections with bounds-checked access, and use Java Streams to avoid manual indexing.
Q30. What is ClassCastException?
A: ClassCastException is thrown when code attempts to cast an object to a class of which it is not an instance. This is an unchecked exception. Prevention: use the instanceof operator before casting, use Java generics to enforce type safety at compile time, and avoid raw types. Java 16+ introduced pattern matching instanceof (if (obj instanceof String s)) which combines the check and cast, eliminating explicit casts.
Q31. What is IOException and its common subclasses?
A: java.io.IOException is a checked exception representing failures in I/O operations (file read/write, network communication, etc.). Common subclasses: FileNotFoundException (file does not exist or cannot be opened), EOFException (end of file reached unexpectedly), SocketException (socket-level error), ConnectException (connection refused), SSLException (SSL handshake failure), and InterruptedIOException (I/O interrupted by thread interrupt). Always close streams in finally/try-with-resources even when IOException is caught.
Q32. What is SQLException?
A: java.sql.SQLException is a checked exception thrown when a database access error occurs or other JDBC errors happen. It provides an error code (getErrorCode()), a SQLState string (getSQLState()), and supports exception chaining via getNextException() and setNextException(). Common subclasses include SQLIntegrityConstraintViolationException, SQLTimeoutException, SQLSyntaxErrorException, and BatchUpdateException.
Q33. What is ClassNotFoundException vs NoClassDefFoundError?
A: ClassNotFoundException is a checked exception thrown when an application tries to load a class by name (using Class.forName(), ClassLoader.loadClass(), etc.) and the class cannot be found in the classpath. NoClassDefFoundError is an Error thrown when the JVM cannot find a class that was available at compile time but is missing at runtime, or when the class failed to initialize (ExceptionInInitializerError). ClassNotFoundException is recoverable (handle with catch); NoClassDefFoundError indicates a classpath configuration or initialization problem.
Q34. Can you catch multiple exceptions with different handling per type?
A: Yes. You can chain multiple catch blocks with different exception types and different handling logic. Catch blocks are evaluated in order from top to bottom, so more specific exception types must appear before their supertypes. If a more general type (like Exception) appears first, it will catch everything and the more specific catch blocks below it will be unreachable (compiler error for checked exceptions).
try {
riskyOperation();
} catch (FileNotFoundException e) {
// Most specific — file missing
createDefaultFile();
} catch (IOException e) {
// Less specific — general I/O failure
notifyAdmin(e);
} catch (Exception e) {
// Catch-all — unexpected error
logger.error("Unexpected error", e);
throw new RuntimeException("Unexpected", e);
}
Q35. What happens if a finally block throws an exception?
A: If the finally block throws an exception, it replaces any exception that was being propagated from the try or catch block. The original exception is lost completely (unlike try-with-resources which preserves it as a suppressed exception). This is a major pitfall with traditional try-finally. Best practice: never throw exceptions from finally blocks, or use try-with-resources which handles this correctly via suppressed exceptions.
Q36. What does initCause() do?
A: initCause(Throwable cause) is a method on Throwable that sets the cause of the exception after construction. It can only be called once per Throwable instance (calling it again throws IllegalStateException). It is useful when using legacy constructors that predate the cause-accepting constructors introduced in Java 1.4. The preferred approach is to use the constructor that accepts a cause directly rather than calling initCause() separately.
Q37. What is the difference between getCause() and getSuppressed()?
A: getCause() returns the exception that caused this exception to be thrown — the "reason" for the exception, typically set via exception chaining. getSuppressed() returns an array of exceptions that were suppressed (typically during try-with-resources close() calls) in order to deliver the primary exception. getCause() is singular (one cause), while getSuppressed() can return multiple exceptions. Both are methods on Throwable.
Q38. How does try-with-resources handle multiple resources?
A: Multiple resources can be declared in a single try-with-resources statement, separated by semicolons. They are initialized in left-to-right order but closed in right-to-left (reverse) order, mirroring LIFO stack discipline. If initialization of a later resource fails, all previously initialized resources are still closed. This ensures correct cleanup even during partial initialization.
// Opened: conn first, then stmt, then rs
// Closed: rs first, then stmt, then conn
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
process(rs);
}
} // rs, stmt, conn closed automatically in reverse order
Q39. Can we have a try block without a catch block?
A: Yes. In Java, you can have a try block without a catch block if there is a finally block (try-finally). You can also have try-with-resources without a catch block — the resource will be closed automatically. You cannot have a try block alone with neither catch nor finally. The try-finally without catch is commonly used when you want to ensure cleanup but do not want to handle exceptions at that layer.
Q40. What is the purpose of the throws clause in a method signature?
A: The throws clause is a contract declaration that informs callers which checked exceptions the method might propagate. It enables the compiler to enforce that callers either catch those exceptions or declare them in their own throws clause. It also serves as documentation. Including unchecked exceptions in throws is legal but optional and generally not recommended (they clutter the API without adding enforcement value). For abstract methods and interfaces, throws clauses define the API contract that implementations should honor.
Q41. Is it possible to rethrow an exception with a different message?
A: Not directly — Throwable's message is set at construction and there is no setter. To add context: (1) wrap the original exception in a new exception with a different message (exception chaining). (2) Create a new instance of the same exception type with the new message and original as cause. Approach (1) is standard. This is why using exception chaining (wrapping) with context-specific messages is the idiomatic Java pattern.
Q42. What is StackTraceElement and how do you use getStackTrace()?
A: getStackTrace() on a Throwable returns an array of StackTraceElement objects, each representing one stack frame. Each StackTraceElement provides: getClassName(), getMethodName(), getFileName(), and getLineNumber(). This is useful for logging or custom stack trace formatting. printStackTrace() prints the full stack trace to stderr by default, or to a PrintStream/PrintWriter if provided.
Q43. What is fillInStackTrace()?
A: fillInStackTrace() is a method on Throwable that re-initializes the stack trace information to the current call stack. When you rethrow an exception, the original stack trace is preserved. Calling fillInStackTrace() on an exception before rethrowing replaces the stack trace with the current rethrow location — useful when creating a fresh propagation context. It returns the Throwable itself (for chaining). Overriding fillInStackTrace() to return this without calling super is a common optimization for exceptions used as control flow (avoids expensive stack capture).
Q44. What is the performance cost of exceptions?
A: Creating an exception is expensive primarily because of fillInStackTrace(), which walks the thread stack and captures all frames. Throwing and catching exceptions also has overhead. For this reason: (1) Never use exceptions for normal control flow (e.g., breaking out of loops). (2) In performance-critical code, pre-create exception instances (but this is rare). (3) Override fillInStackTrace() to return this for custom exceptions used as signals, disabling the stack capture. The actual throw/catch mechanism itself is relatively cheap; the expense is stack trace capture.
Q45. Can you throw null in Java?
A: You can write throw null, but at runtime the JVM will throw a NullPointerException instead of null, because throw requires a non-null Throwable reference. So throw null is legal syntax but immediately causes a NullPointerException at the throw statement itself.
Q46. What is the difference between Exception and RuntimeException?
A: RuntimeException is a subclass of Exception. Exceptions that are subclasses of RuntimeException are called unchecked exceptions — the compiler does not require you to catch or declare them. All other Exception subclasses (that are not RuntimeExceptions) are checked exceptions. RuntimeException represents programming errors (bugs), while checked Exceptions represent conditions callers should reasonably be able to recover from. This distinction was part of the original Java design and is enforced purely at compile time; the JVM treats all Throwables identically at runtime.
Q47. Can an interface method declare checked exceptions?
A: Yes. Interface methods can declare checked exceptions in their throws clause. Implementing classes can then throw any subset of those exceptions (fewer or narrower), but cannot add new checked exceptions not declared in the interface. This means a caller programming to the interface only needs to handle the exceptions declared in the interface, regardless of which implementation is used at runtime — a key aspect of Liskov Substitution and clean API design.
Q48. What is UnsupportedOperationException?
A: UnsupportedOperationException (a RuntimeException subclass) is thrown to indicate that the requested operation is not supported. It is commonly thrown by immutable collection implementations (e.g., Collections.unmodifiableList(), List.of()) when mutation methods like add(), remove(), or set() are called. It is also used in skeleton/template method patterns where subclasses are expected to provide concrete implementations.
Q49. What is ArithmeticException?
A: ArithmeticException is an unchecked exception thrown when an exceptional arithmetic condition occurs. The most common case is integer division or modulo by zero: int x = 10 / 0; throws ArithmeticException with the message "/ by zero". Note that floating-point division by zero does NOT throw an exception in Java — it returns positive infinity, negative infinity, or NaN per IEEE 754 semantics.
Q50. What is NumberFormatException?
A: NumberFormatException extends IllegalArgumentException and is thrown when code attempts to convert a string to a numeric type but the string is not in the appropriate format. Common triggers: Integer.parseInt("abc"), Double.parseDouble("xyz"). Prevention: validate input format before parsing (e.g., using regex), or use try-catch to handle the exception and provide a meaningful error message to the user.
Q51. What is IllegalArgumentException and when should you use it?
A: IllegalArgumentException is thrown to indicate that a method has been passed an illegal or inappropriate argument. It is the standard way to validate method parameters in Java (alongside Objects.requireNonNull() for null checks). It is an unchecked exception (RuntimeException subclass) because invalid arguments are programming errors. Common usage: if (age < 0) throw new IllegalArgumentException("Age cannot be negative: " + age);
Q52. What is IllegalStateException?
A: IllegalStateException is thrown when a method is called at an inappropriate time or when the object is in the wrong state to handle the request. For example, calling Iterator.remove() before calling Iterator.next(), or building a configuration object after it has already been built. It is an unchecked exception and signals a programming error in the calling code.
Q53. What is ConcurrentModificationException?
A: ConcurrentModificationException is thrown by iterators when the collection is structurally modified during iteration by any means other than the iterator's own remove() method. It uses a fail-fast mechanism (modCount comparison) to detect concurrent modification quickly rather than allowing silent data corruption. To modify a collection during iteration, use the Iterator's remove() method, or use a CopyOnWriteArrayList, or collect changes and apply them after iteration.
Q54. What is an Error vs Exception — should you ever catch Error?
A: In general, Errors should not be caught because they represent unrecoverable JVM conditions (OutOfMemoryError, StackOverflowError, etc.). However, there are specific valid cases for catching Errors: (1) Application containers/frameworks may catch Throwable to log and cleanly shut down. (2) Test frameworks catch AssertionError and similar. (3) ThreadDeath (not an Error subclass but worth noting) may be caught and rethrown. The rule is: if you catch an Error, you must rethrow it (or a superset) after your handling, not swallow it. Never catch Error in normal business logic.
Q55. How do you create an exception with both a message and a cause?
A: Use the constructor that accepts both String message and Throwable cause: new MyException("Something failed while processing order " + orderId, originalCause). This is the preferred way to create exceptions in service or repository layers. The message should contain enough context (IDs, states, inputs) to diagnose the issue without needing the full stack trace. The cause provides the chain back to the root technical error.
Q56. What is the difference between Closeable and AutoCloseable?
A: java.io.Closeable extends java.lang.AutoCloseable. The key difference: AutoCloseable's close() method declares throws Exception (broadest), while Closeable's close() declares throws IOException (narrower). Both allow use in try-with-resources. Closeable additionally specifies that calling close() multiple times is idempotent (no harmful effects), while AutoCloseable makes no such guarantee. Prefer Closeable for I/O resources; use AutoCloseable for other resources that should only be closed once.
Q57. What is VirtualMachineError?
A: java.lang.VirtualMachineError is an abstract Error subclass thrown to indicate that the JVM is broken or has run out of resources necessary for it to continue operating. Its concrete subclasses include OutOfMemoryError, StackOverflowError, InternalError, and UnknownError. These represent fundamental JVM-level failures rather than application errors and should never be caught in normal code.
Q58. What is AssertionError?
A: AssertionError is thrown when a Java assertion statement (assert condition : message) fails. Assertions must be enabled with the JVM flag -ea (or -enableassertions) — they are disabled by default in production. AssertionError extends Error, not Exception. It is used for invariant checking during development and testing. Do not use assertions to validate user input or public API parameters; use explicit if-checks with IllegalArgumentException for those.
Q59. What is ThreadDeath?
A: ThreadDeath is a subclass of Error thrown when a thread is stopped via the deprecated Thread.stop() method. If caught, it should always be rethrown to actually stop the thread. Its presence as an Error (not Exception) means that a catch (Exception e) block will not catch it, allowing the stop to propagate naturally even through exception handlers. Thread.stop() is deprecated because stopping a thread asynchronously can leave shared objects in inconsistent states.
Q60. What is LinkageError?
A: LinkageError is an Error subclass indicating that a class has a dependency on another class that has changed incompatibly since the dependent class was compiled. Common subclasses include NoClassDefFoundError, NoSuchMethodError, NoSuchFieldError, IncompatibleClassChangeError, and UnsatisfiedLinkError (for native methods). These typically indicate classpath/version conflicts (e.g., two different versions of a library on the classpath).
Q61. What is the Exception constructor signature?
A: The standard four constructors of Exception (and most of its subclasses) are: (1) Exception() — no-arg. (2) Exception(String message) — with message. (3) Exception(String message, Throwable cause) — with message and cause. (4) Exception(Throwable cause) — cause only; message defaults to cause.toString(). There is also a protected constructor Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) added in Java 7 for performance-sensitive custom exceptions (e.g., disabling stack trace capture).
Q62. When should you use checked vs unchecked exceptions for custom exceptions?
A: Use checked exceptions when: the caller can reasonably recover from the situation and you want the compiler to force them to handle it (e.g., FileNotFoundException when loading optional config). Use unchecked exceptions (RuntimeException subclasses) for: programming errors the caller cannot reasonably recover from, API validation failures, and any situation where declaring the exception in every calling method would add noise without value. Many modern Java frameworks (Spring, Hibernate) prefer unchecked exceptions for this reason. Joshua Bloch (Effective Java) recommends checked exceptions only for recoverable conditions, unchecked for everything else.
Q63. What is the difference between throw new Exception() and throw e?
A: throw new Exception() creates and throws a brand-new exception, with a fresh stack trace starting at the current point. throw e rethrows an existing exception, preserving its original stack trace (the stack trace is already captured in the exception object). When rethrowing to preserve context, always use throw e not throw new Exception(e.getMessage()) (which would lose the original stack trace). To add context while preserving the original, use exception chaining: throw new ServiceException("context", e).
Q64. Can a static method throw an exception?
A: Yes. Static methods can throw both checked and unchecked exceptions, declared with the throws clause just like instance methods. There is no difference in exception-throwing semantics between static and instance methods. Static initializers (static blocks) can also throw unchecked exceptions (which get wrapped in ExceptionInInitializerError), but cannot throw checked exceptions directly.
Q65. What is a chained exception example with multiple layers?
A: Exception chaining across multiple layers preserves the full context from infrastructure through to business logic:
// Repository layer
public User findUser(int id) throws DataAccessException {
try {
return jdbcTemplate.queryForObject(sql, User.class, id);
} catch (EmptyResultDataAccessException e) {
throw new UserNotFoundException("User not found: " + id, e);
}
}
// Service layer
public UserDTO getUser(int id) throws ServiceException {
try {
return mapper.toDTO(userRepo.findUser(id));
} catch (UserNotFoundException e) {
throw new ServiceException("Cannot serve user request for id=" + id, e);
}
}
// Inspecting the chain
try {
service.getUser(42);
} catch (ServiceException e) {
System.out.println(e.getMessage()); // ServiceException message
System.out.println(e.getCause()); // UserNotFoundException
System.out.println(e.getCause().getCause()); // EmptyResultDataAccessException
}
Q66. Can you declare a throws clause on an overriding method with no exceptions?
A: Yes. An overriding method can have a completely empty throws clause (or omit it entirely) even if the parent method declares checked exceptions. This is valid because callers of the parent interface will still handle the declared exceptions, and the overriding implementation simply doesn't throw them — it narrows the contract, which is safe.
Q67. What happens if you call getCause() on an exception that has no cause?
A: getCause() returns null if no cause was set (either because none was provided to the constructor, or because initCause() was never called). Code that traverses exception chains should always null-check before calling getCause() recursively to avoid NPE.
Q68. What is ExceptionInInitializerError and how does it propagate?
A: When a static initializer or static field initializer throws an unchecked exception, the JVM wraps it in ExceptionInInitializerError and records the class as failed to initialize. Subsequent attempts to use the class (Class.forName(), new ClassName(), or accessing static fields) will throw NoClassDefFoundError (not ExceptionInInitializerError again), because the class is permanently marked as failed. This is important to understand when debugging startup failures in frameworks that use extensive static initialization.
Q69. What is VerifyError?
A: VerifyError is a subclass of LinkageError thrown when the JVM's bytecode verifier detects a class file that is not well-formed or violates bytecode constraints (e.g., stack underflow, type mismatches). This typically occurs when a class file is corrupted, was compiled by a broken compiler, or was manually modified. It is distinct from compilation errors — it occurs at class-loading time.
Q70. What is the try-finally pattern for non-AutoCloseable resources?
A: For resources that do not implement AutoCloseable, use explicit try-finally to ensure cleanup. Always initialize the resource variable to null before the try block so you can null-check in the finally block (in case initialization itself fails). Call cleanup only if the resource was successfully acquired.
Lock lock = new ReentrantLock();
lock.lock(); // acquire before try
try {
// critical section
doWork();
} finally {
lock.unlock(); // always release
}
// For nullable resource:
Connection conn = null;
try {
conn = dataSource.getConnection();
doWork(conn);
} finally {
if (conn != null) {
try { conn.close(); } catch (SQLException ignored) {}
}
}
Q71. Can you use return inside a finally block?
A: Yes, but it is strongly discouraged. A return in the finally block overrides any return in the try or catch block, effectively discarding the try/catch return value. Worse, if an exception was being propagated, the finally-block return swallows it silently — the method returns normally as if nothing happened. This is a subtle bug source. Most static analysis tools flag return-in-finally as an error or warning.
Q72. What is the meaning of "Exception in thread main"?
A: "Exception in thread main" is the JVM's message printed to stderr when an uncaught exception propagates all the way to the top of the main thread's call stack. The JVM calls the thread's UncaughtExceptionHandler, which by default prints the exception class name and message followed by the stack trace. All other threads continue running. If it is the main thread and no daemon threads are alive, the JVM exits.
Q73. What is UncaughtExceptionHandler?
A: Thread.UncaughtExceptionHandler is a functional interface with method uncaughtException(Thread t, Throwable e) called by the JVM when a thread is about to terminate due to an uncaught exception. You can set a per-thread handler with thread.setUncaughtExceptionHandler() or a JVM-wide default with Thread.setDefaultUncaughtExceptionHandler(). This is useful for centralized exception logging, crash reporting, or restarting worker threads.
Q74. How does Spring Framework handle exceptions?
A: Spring typically uses unchecked exceptions (RuntimeExceptions). Key aspects: (1) @ExceptionHandler methods in @Controller/@RestController handle exceptions locally. (2) @ControllerAdvice (global) centralizes exception handling across all controllers. (3) ResponseEntityExceptionHandler provides a base class with pre-built handlers for standard Spring MVC exceptions. (4) Spring Data translates vendor-specific exceptions (SQLException) into a Spring-specific hierarchy (DataAccessException). (5) @Transactional rolls back on RuntimeException by default; you can configure rollbackFor for checked exceptions.
Q75. What is the Java 7 precise rethrow feature?
A: Java 7 introduced "precise rethrow" — when you catch a broad exception type but only rethrow it, the compiler is smart enough to know that only the specific exception types from the try block can actually be thrown. This allows you to write a more specific throws clause even though the catch variable is of a broader type. Before Java 7, catching Exception and rethrowing it required declaring throws Exception.
// Java 7+ precise rethrow
// Only IOException or SQLException can actually come from the try block
public void process() throws IOException, SQLException {
try {
riskyIO(); // throws IOException
riskySQL(); // throws SQLException
} catch (Exception e) {
logger.error("Error", e);
throw e; // Java 7+: compiler knows only IOException|SQLException possible
}
}
Q76. What is OutOfMemoryError: Java heap space vs PermGen/Metaspace?
A: "OutOfMemoryError: Java heap space" means the heap (where objects live) is exhausted — too many live objects, memory leaks, or heap too small. In Java 7 and earlier, "OutOfMemoryError: PermGen space" meant the Permanent Generation (class metadata area) was full — typically from classloader leaks or loading too many classes. In Java 8+, PermGen was replaced by Metaspace (native memory), so the error becomes "OutOfMemoryError: Metaspace". Fix heap OOM by increasing heap (-Xmx), fixing leaks, or reducing object retention.
Q77. How do you propagate an exception across thread boundaries?
A: Exceptions thrown in a thread do not automatically propagate to the spawning thread. Options: (1) Use Future.get() — ExecutorService wraps any exception thrown by a Callable into an ExecutionException, retrievable via cause = ee.getCause(). (2) Use CompletableFuture's exceptionally(), handle(), or whenComplete() for async pipelines. (3) Shared exception reference with AtomicReference. (4) UncaughtExceptionHandler for fire-and-forget threads.
ExecutorService exec = Executors.newSingleThreadExecutor();
Future<String> future = exec.submit(() -> {
if (someCondition) throw new IOException("Worker failed");
return "result";
});
try {
String result = future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause(); // the original IOException
logger.error("Task failed", cause);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupt flag
}
Q78. What is InterruptedException and how should you handle it?
A: InterruptedException is a checked exception thrown when a thread is waiting, sleeping, or otherwise occupied and is interrupted. Handling best practices: (1) If your method can propagate it, declare it with throws InterruptedException. (2) If you must catch it (e.g., in a Runnable), restore the interrupt flag: Thread.currentThread().interrupt(), then handle or exit. (3) Never swallow InterruptedException without restoring the interrupt status — this breaks thread cancellation mechanisms. (4) Avoid catching and ignoring it in loops — it prevents clean shutdown.
Q79. What is the difference between printStackTrace() and logging an exception?
A: printStackTrace() writes to stderr (or a specified stream) synchronously and cannot be easily formatted, filtered, or directed to different outputs. Logging frameworks (SLF4J+Logback, Log4j2) offer: configurable log levels, asynchronous logging, structured output (JSON), multiple appenders (file, console, network), correlation IDs, and the ability to enable/disable at runtime without code changes. In production, always use a proper logging framework instead of printStackTrace(). Pass the exception as the second argument: logger.error("Error processing", e) — this captures the full stack trace.
Q80. What is exception masking?
A: Exception masking occurs when a finally block (or a close() method) throws a new exception, replacing and hiding the primary exception from the try block. The original exception is "masked" — it is lost with no trace unless you explicitly preserve it. Try-with-resources in Java 7+ solved this by using suppressed exceptions instead of masking, attaching the secondary exception to the primary via addSuppressed() rather than replacing it.
Q81. Can you have nested try-catch blocks?
A: Yes. Try-catch blocks can be arbitrarily nested. Inner try blocks can catch specific exceptions and handle them locally, while outer try blocks can catch exceptions not handled by inner blocks. This is common when doing multi-step operations where each step has different error recovery strategies. However, deeply nested try blocks can indicate code that needs refactoring — consider extracting methods to reduce nesting depth.
Q82. What is a NegativeArraySizeException?
A: NegativeArraySizeException is an unchecked exception thrown when code attempts to create an array with a negative size (e.g., new int[-5]). It is a programming bug — array sizes must be non-negative. It extends RuntimeException and is typically caused by a calculation error in determining the array size.
Q83. What is the try-catch overhead in tight loops?
A: In modern JVMs (HotSpot), a try-catch block that does NOT throw has essentially zero overhead in normal execution — the JIT compiler handles this efficiently. The cost only manifests when an exception is actually thrown (stack trace capture is expensive). Therefore: (1) Do not avoid try-catch out of performance concern in normal code. (2) Do avoid using exception throwing as part of normal control flow in tight loops (e.g., catching NoSuchElementException to detect loop end). (3) Pre-check conditions (hasNext(), containsKey()) rather than catching exceptions for expected cases.
Q84. What is StringIndexOutOfBoundsException?
A: StringIndexOutOfBoundsException extends IndexOutOfBoundsException and is thrown when code accesses a String at an invalid index (negative, or >= length). It is thrown by String's charAt(), substring(), and similar methods. It is an unchecked exception representing a programming error. Always validate string indices against str.length() before accessing characters.
Q85. What is StackOverflowError vs OutOfMemoryError — how do you distinguish them at runtime?
A: At runtime they are distinct Error subclasses with clear class names. StackOverflowError's stack trace typically shows the same method repeated hundreds of times (revealing the infinite recursion). OutOfMemoryError's stack trace may point to an allocation site (new MyObject(), array allocation, etc.) and may include the subtype in the message ("Java heap space", "Metaspace", "unable to create new native thread"). Heap dumps (triggered by -XX:+HeapDumpOnOutOfMemoryError) help diagnose OOM; reducing recursion depth or adding a base case fixes StackOverflow.
Q86. How do you write a unit test that verifies an exception is thrown?
A: With JUnit 5, use assertThrows():
import static org.junit.jupiter.api.Assertions.*;
@Test
void testDivideByZero() {
Calculator calc = new Calculator();
ArithmeticException ex = assertThrows(
ArithmeticException.class,
() -> calc.divide(10, 0)
);
assertEquals("/ by zero", ex.getMessage());
}
// JUnit 5: assertDoesNotThrow
@Test
void testValidInput() {
assertDoesNotThrow(() -> calc.divide(10, 2));
}
// JUnit 4 equivalent
@Test(expected = ArithmeticException.class)
public void testDivideByZero_JUnit4() {
calc.divide(10, 0);
}
Q87. What is a checked exception controversy in Java?
A: Checked exceptions are controversial because: (1) They pollute API signatures and callers throughout the call chain. (2) They lead to checked exception tunneling — wrapping in RuntimeException to escape the checked constraint. (3) They are often swallowed because developers are forced to catch them but don't know how to handle them. (4) Modern languages (Kotlin, Scala, C#) abandoned checked exceptions. Arguments for: they make failure modes explicit and force callers to think about error handling. The consensus today leans toward using unchecked exceptions for most cases and checked only when callers can realistically recover.
Q88. What is exception tunneling?
A: Exception tunneling is the practice of wrapping a checked exception inside an unchecked exception to avoid declaring it in intermediate method signatures. For example, wrapping an IOException in a RuntimeException to pass it through layers that don't handle I/O. This is sometimes necessary (e.g., when working with interfaces that don't declare checked exceptions, like Runnable). The original cause should always be preserved via exception chaining so it is not lost.
Q89. What are the Throwable methods available for exception inspection?
A: Key Throwable methods: getMessage() — developer-facing message string. getLocalizedMessage() — locale-specific message (default is getMessage()). getCause() — the chained cause exception. getStackTrace() — array of StackTraceElement. printStackTrace() — print stack trace to stderr. getSuppressed() — suppressed exceptions array (Java 7+). addSuppressed(Throwable) — add a suppressed exception. initCause(Throwable) — set cause (once). fillInStackTrace() — repopulate stack trace. toString() — class name + message.
Q90. What is the difference between System.exit() and throwing an exception?
A: System.exit(status) terminates the JVM immediately (after running registered shutdown hooks), preventing any finally blocks, catch blocks, or stack unwinding from occurring. Throwing an exception propagates up the call stack through catch and finally blocks, allowing for resource cleanup and graceful handling. System.exit() should be used only in command-line applications to signal exit codes. In server-side code, throwing exceptions is almost always the right choice — it allows the container or framework to handle the failure gracefully.
Q91. What is a global exception handler in a web application?
A: A global exception handler catches uncaught exceptions at the application boundary and converts them to appropriate HTTP responses (status codes, error bodies). In Spring MVC, @ControllerAdvice + @ExceptionHandler provides this. In JAX-RS, implement ExceptionMapper<T>. In Servlet-based apps, implement HttpServlet's error handling. The global handler centralizes error response formatting, prevents stack traces from leaking to clients, logs errors consistently, and returns structured error payloads (e.g., RFC 7807 Problem Details JSON).
Q92. What is the addSuppressed() method used for?
A: addSuppressed(Throwable exception) adds an exception to the list of exceptions suppressed by this exception, which were suppressed in order to deliver the current exception. This is called by the JVM automatically in try-with-resources when the close() method of a resource throws an exception after the try block has already thrown. Application code can also call addSuppressed() manually when building exception aggregation patterns. The suppressed exceptions are available via getSuppressed().
Q93. What is fail-fast vs fail-safe in exception handling?
A: Fail-fast means detecting and reporting errors immediately when they occur, rather than trying to continue in a potentially inconsistent state. Java's collection iterators are fail-fast (throw ConcurrentModificationException). In exception handling, fail-fast means validating preconditions at method entry and throwing immediately if violated. Fail-safe means attempting to continue even in the presence of errors, potentially with reduced functionality. Fail-fast is generally preferred in development (bugs surface quickly); fail-safe patterns (circuit breakers, fallbacks) are used in production distributed systems for resilience.
Q94. What is the relationship between exception handling and transactions?
A: In transactional systems, exceptions drive rollback decisions. In Spring: @Transactional automatically rolls back the transaction when an unchecked exception (RuntimeException/Error) propagates out of the method. Checked exceptions do NOT trigger rollback by default (you must configure rollbackFor). You can also use noRollbackFor to prevent rollback on specific unchecked exceptions. Never swallow exceptions inside a @Transactional method if you want the transaction to roll back — the exception must propagate out of the transactional boundary.
Q95. What is the best practice for exception messages?
A: Exception messages should be: (1) Descriptive — state what failed, not just that it failed. (2) Context-rich — include relevant IDs, values, and state. (3) Actionable — help the reader understand what to do or investigate. (4) Developer-facing, not user-facing (user-facing messages should be in the UI layer). Example of a poor message: "Error occurred". Example of a good message: "Failed to save Order id=7432: constraint violation on field 'email' value='user@test.com' — duplicate entry". Including the offending value in the message dramatically speeds up debugging.
Q96. What is defensive programming in the context of exceptions?
A: Defensive programming means writing code that validates its assumptions and fails fast with clear error messages when those assumptions are violated, rather than operating silently on bad data. In Java: use Objects.requireNonNull() for null checks, explicit range checks for numeric inputs, and precondition assertions for invariants. Defensive programming prevents subtle bugs from propagating deep into business logic where the original cause is hard to trace. Libraries like Guava's Preconditions class provide a fluent API for this pattern.
Q97. What is exception handling in Java streams?
A: Java Streams' lambda-based API does not directly support checked exceptions. Solutions: (1) Wrap checked exceptions in RuntimeException inside the lambda. (2) Use a utility wrapper method. (3) Use a custom functional interface with checked exception declaration. (4) Use libraries like vavr's Try monad for functional error handling. Avoid letting exceptions escape streams silently — they will propagate as RuntimeExceptions and the stream will terminate early without processing remaining elements, unless you handle them per-element inside the lambda.
Q98. What is the difference between error handling with Optional vs exceptions?
A: Optional<T> is for representing a value that may or may not be present (absence is expected, not exceptional). Exceptions are for representing failure conditions that are abnormal. Use Optional for queries that may return no result (findById()); use exceptions for operations that must succeed but failed due to an unexpected condition. Mixing them (returning Optional.empty() when an IOException occurred) hides errors. Optional is not a substitute for exception handling — it is a container for optional values in normal flow.
Q99. What is the Java 14+ helpful NullPointerException feature?
A: Java 14 introduced "JEP 358: Helpful NullPointerExceptions" which generates descriptive NPE messages identifying exactly which variable was null. For example, instead of "NullPointerException" alone, the message says "Cannot invoke 'String.length()' because 'str' is null" or "Cannot store to 'int[]' because 'arr' is null". This is enabled by default in Java 14+ and dramatically reduces debugging time. The feature is controlled by the JVM flag -XX:+ShowCodeDetailsInExceptionMessages (enabled by default in Java 14+).
Q100. What is the pattern for resource cleanup when constructor can throw?
A: When a constructor acquires multiple resources sequentially and an intermediate one fails, previously acquired resources must be released in the constructor itself (not relying on the caller, since the object reference was never returned). This requires manual try-finally inside the constructor:
class MultiResourceHolder {
private final Connection conn;
private final PreparedStatement stmt;
public MultiResourceHolder(DataSource ds, String sql) throws SQLException {
conn = ds.getConnection(); // acquired
try {
stmt = conn.prepareStatement(sql); // may fail
} catch (SQLException e) {
try { conn.close(); } catch (SQLException suppressed) {
e.addSuppressed(suppressed);
}
throw e; // rethrow after cleanup
}
}
public void close() throws SQLException {
try { stmt.close(); } finally { conn.close(); }
}
}
Q101. What is the Lombok @SneakyThrows annotation?
A: Lombok's @SneakyThrows is an annotation that generates bytecode to throw checked exceptions without declaring them in the method's throws clause, using the sneaky-throw technique internally. At the source level, you write the checked exception throw; Lombok transforms it at compile time so the compiler doesn't see it as a checked exception. It is useful in lambdas, Runnable implementations, and framework callbacks where you cannot change the functional interface signature. It should be used judiciously — it reduces API clarity since callers are not informed about the possible exceptions.
Q102. What are the key differences between StackOverflowError and OutOfMemoryError in terms of recoverability?
A: StackOverflowError is more recoverable — it only affects the thread that overflowed its stack. The thread terminates abnormally, but other threads continue. In theory, you could catch a StackOverflowError and allow the thread to continue if you are very careful. OutOfMemoryError is generally not recoverable — the JVM heap is exhausted, and even creating a String for a log message may fail. Some OOM variants (e.g., "unable to create new native thread") may be more transient. After either error, the JVM's state may be inconsistent; generally, the safest response to either is orderly shutdown and restart.
Q103. What are best practices for logging exceptions?
A: (1) Always pass the exception as the last argument to the logging method — this captures the full stack trace. (2) Log at the appropriate level: ERROR for unexpected failures, WARN for expected degraded behavior, INFO/DEBUG for normal operational info. (3) Log once — at the top-level handler — not at every layer. (4) Include relevant context (request ID, user ID, input values) using MDC (Mapped Diagnostic Context) or structured logging fields. (5) Do not log and rethrow at every layer (log-and-rethrow at the boundary, rethrow-only at intermediate layers). (6) Ensure sensitive data (passwords, PII) is not in exception messages or logs.
Q104. What is the difference between catch (Exception e) and catch (Throwable t)?
A: catch (Exception e) catches all Exception subclasses (checked and unchecked) but NOT Errors. catch (Throwable t) catches everything — Exceptions and Errors. In most application code, catching Throwable is inappropriate because it catches Errors like OutOfMemoryError that the application cannot meaningfully handle. There are legitimate framework-level uses (e.g., application servers, test frameworks) where catching Throwable makes sense to ensure cleanup or reporting. Always rethrow Errors if you catch them at the Throwable level.
Q105. What is the Java exception handling mental model for API design?
A: Design exception handling as part of your API contract: (1) Checked exceptions are part of the method's return type contract — they are alternative outcomes callers must handle. (2) Unchecked exceptions are precondition violations — callers should ensure inputs are valid. (3) Use exception hierarchies to allow callers to catch at the right granularity. (4) Document all exceptions in Javadoc with @throws, including unchecked ones that callers should know about. (5) Provide specific exception types (UserNotFoundException) rather than generic ones (RuntimeException) to allow selective catching. (6) Be consistent — if similar methods throw the same exception type for the same condition, callers can write uniform error handling.
Post a Comment
Add