Java Exceptions Interview Prep
Java Exceptions Interview Prep
Exception Handling
Master Reference for 3-Year Experience Level
Answer:
Java's exception family starts at Throwable, which has two direct subclasses: Error and Exception.
Error: Represents serious JVM problems (OutOfMemoryError, StackOverflowError). You should almost never catch
these.
Exception: The branch you work with. It splits into two categories:
→ Checked exceptions (e.g., IOException, SQLException) — must be declared or caught.
→ Unchecked exceptions extend RuntimeException (e.g., NullPointerException, IllegalArgumentException).
Throwable
├── Error (JVM-level, do NOT catch)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── AssertionError
└── Exception
├── IOException (Checked)
💡 Interviewer Tip:
Interviewers expect you to say 'unchecked = extends RuntimeException' without hesitation. They'll also ask:
'Can you catch an Error?' — technically yes, but you almost never should.
Answer:
Checked exceptions are verified by the compiler. If a method can throw one, you must either catch it or declare it with
'throws'. Examples: IOException, FileNotFoundException.
Unchecked exceptions extend RuntimeException and are not checked at compile time. They typically represent
programming mistakes — null dereferences, bad array indices, illegal arguments.
The key philosophical difference: checked exceptions model recoverable conditions the caller should handle (file not
found, network timeout). Unchecked exceptions model bugs the programmer should fix.
💡 Interviewer Tip:
Follow-up: 'Should all exceptions be checked?' This triggers a discussion of the modern Java architectural
debate — Spring uses exclusively unchecked exceptions for a reason.
Q3 What happens in the finally block — when exactly does it NOT execute? [Basic]
Answer:
The finally block runs after try/catch regardless of whether an exception was thrown or caught — with exactly two
exceptions:
1. [Link]() is called — the JVM terminates before finally runs.
2. The JVM crashes or the thread is killed (e.g., via [Link]() or power loss).
Importantly: finally DOES run even if there is a return in the try block. The finally executes, then the return value is sent.
💡 Interviewer Tip:
The classic trap: 'What if finally has a return statement?' Answer: it swallows the try's return value. Interviewers
love this. Also ask: 'What if both catch and finally throw exceptions?' — the finally exception wins.
Answer:
Try-with-resources (Java 7+) automatically closes any resource that implements AutoCloseable. The close() method is
called in the finally block by the compiler.
You can declare multiple resources — they close in REVERSE order of declaration.
If both the try body and close() throw exceptions, the try body's exception is propagated and close()'s exception is
added as a suppressed exception (getSuppressed()).
💡 Interviewer Tip:
Follow-up: 'What are suppressed exceptions?' This leads to [Link]() — a distinguishing
advanced answer.
Q5 What are suppressed exceptions and how do you access them? [Intermediate]
Answer:
When a try-with-resources block closes a resource and BOTH the body AND close() throw exceptions, Java needs to
decide which to propagate.
The primary exception (from the try body) is propagated. The secondary one (from close()) is attached as a 'suppressed'
exception on the primary.
You retrieve them via [Link]() which returns an array of Throwable.
💡 Interviewer Tip:
Many candidates know try-with-resources but blank on suppressed exceptions. Mentioning
[Link]() and the fact this was introduced alongside TWR in Java 7 will impress.
Answer:
Yes. A try block can be followed by just a finally block with no catch at all. This is valid and useful when you want to
ensure cleanup happens but don't want to handle the exception (you let it propagate).
You cannot, however, have a try block with neither catch nor finally — that is a compile error.
💡 Interviewer Tip:
Interviewers use this to test if you understand that catch is optional. The natural follow-up: 'Why would you
NOT use try-with-resources here?' Answer: only if you need the resource declared before the try.
Answer:
Exception chaining means wrapping a low-level exception inside a higher-level, more meaningful one — preserving the
original cause in the chain.
This is critical for debugging: you get both the high-level context (e.g., 'User service failed') and the root cause (e.g.,
'Connection refused') in the stack trace.
Two approaches: pass cause to constructor, or call initCause() (older APIs).
💡 Interviewer Tip:
A classic anti-pattern to mention: catch (SQLException e) { throw new RuntimeException("error"); } — this
DROPS the cause, making debugging impossible. Never do this.
Answer:
'throw' is a statement that actually throws an exception object at runtime. It appears inside a method body.
'throws' is a declaration on the method signature that advertises which checked exceptions the method might
propagate. It's a compile-time contract with callers.
You can throw unchecked exceptions without declaring them with 'throws', but it's good practice to still document
them with @throws Javadoc.
💡 Interviewer Tip:
Simple question but interviewers expect crisp, confident answers. Follow-up: 'Can you throw a checked
exception without declaring it with throws?' — no, the compiler will reject it.
Answer:
If the catch block itself throws an exception, the finally block still runs (if present), and then the new exception
propagates up the call stack.
try {
riskyOperation();
} catch (IOException e) {
log(e);
throw new ServiceException("failed", e); // chain e as cause
} finally {
cleanup(); // runs even if catch threw
}
💡 Interviewer Tip:
Interviewers often ask: 'What if both catch and finally throw?' The finally exception wins — the catch's
exception is suppressed/lost (unlike TWR, finally exceptions in plain try blocks are NOT automatically attached
as suppressed).
Q10 Can you catch multiple exceptions in one catch block? How? [Basic]
Answer:
Yes, since Java 7, using the multi-catch syntax with the pipe '|' operator.
The catch parameter is implicitly final in a multi-catch block — you cannot reassign it.
The caught types must not be in an inheritance relationship — catching IOException | FileNotFoundException is a
compile error since FileNotFoundException extends IOException.
// Java 7+ multi-catch
try {
riskyOp();
} catch (IOException | SQLException e) {
// e is effectively final here
[Link]("Operation failed", e);
throw new ServiceException(e);
}
💡 Interviewer Tip:
Multi-catch produces more readable code and avoids duplicated catch bodies. Interviewers expect you to know
the 'effectively final' constraint and the inheritance restriction.
Answer:
ClassNotFoundException (checked exception): Thrown when you try to load a class at runtime via [Link]() or
[Link]() and the class is simply not on the classpath.
💡 Interviewer Tip:
This question comes up frequently in senior interviews. The key distinction: one is about dynamic reflection-
based loading (CNFE), the other is about static references in bytecode (NCDFE). One is a checked Exception, the
other is an Error.
What is the difference between Error and Exception? Should you ever catch an Error?
Q12 [Intermediate]
Answer:
Error signals a serious JVM-level problem that applications normally cannot recover from: OutOfMemoryError,
StackOverflowError, VirtualMachineError.
Exception represents conditions an application might reasonably handle.
You SHOULD NOT catch Errors in general because you cannot meaningfully recover. However, there are two legitimate
exceptions:
1. ThreadDeath — sometimes caught in frameworks managing thread lifecycle.
2. OutOfMemoryError — very occasionally caught to log and gracefully shut down (but you can't rely on it having
enough memory to even log).
Catching Throwable (which catches both) is almost always a code smell unless you're writing a top-level framework or
test harness.
💡 Interviewer Tip:
The answer 'you should never catch Error' is too absolute. Show nuance: frameworks like Spring Boot catch
Throwable at top-level handlers to log and return HTTP 500 rather than crashing silently.
Answer:
When overriding a method, the overriding method can:
→ Throw fewer checked exceptions than the parent.
→ Throw narrower (subclass) checked exceptions.
→ Throw any unchecked exception regardless of what parent declares.
The overriding method CANNOT throw broader or new checked exceptions not declared in the parent. This preserves
Liskov Substitution Principle — callers coded against the parent interface must not be surprised.
class Parent {
void connect() throws IOException { }
}
class Child extends Parent {
// OK: narrower checked exception
@Override void connect() throws FileNotFoundException { }
}
class Bad extends Parent {
// COMPILE ERROR: SQLException not declared in Parent
@Override void connect() throws SQLException { }
}
class AlsoOk extends Parent {
// OK: unchecked exceptions are always allowed
@Override void connect() throws IllegalStateException { }
}
💡 Interviewer Tip:
A guaranteed interview question at the intermediate level. Interviewers like to ask about interface
implementations — the same rules apply: an implementing class cannot declare broader checked exceptions
than the interface method.
Answer:
A well-designed custom exception should:
1. Have a meaningful name that ends in 'Exception' (or 'Error' for severe problems).
2. Provide all four standard constructors: (), (String msg), (String msg, Throwable cause), (Throwable cause).
3. Be unchecked (extend RuntimeException) in most modern applications — checked exceptions add API friction.
4. Include domain-specific fields (error code, resource ID) where useful.
5. Never swallow the cause — always pass it to the super() constructor.
💡 Interviewer Tip:
Interviewers at 3-year level expect the four-constructor pattern. Extra credit: mention that domain-specific
fields (orderId) let global exception handlers return structured JSON error responses in Spring.
Answer:
If a finally block contains a return statement, it overrides any return value in the try block — including any exception
that was being thrown. The exception is silently swallowed.
This is one of the nastiest bugs in Java because no stack trace, no warning — the exception just disappears.
Modern linters (Sonar, IntelliJ) flag this as a critical warning. Never return from finally.
💡 Interviewer Tip:
Interviewers LOVE this question. The correct answer is: 'return in finally silently swallows exceptions — this is
always a bug. SonarQube treats it as a blocker.' It shows you've dealt with real production code issues.
Can you rethrow an exception? How does rethrowing work with type inference in Java
Q16 7+? [Intermediate]
Answer:
You can rethrow a caught exception with 'throw e'. Basic rethrowing is straightforward.
Java 7 introduced precise rethrow: if you catch Exception but only throw a narrower type (and don't reassign the
variable), the compiler is smart enough to know only specific types can escape. You can then declare only those specific
types in the throws clause.
💡 Interviewer Tip:
Most candidates don't know about precise rethrow type inference. Mentioning it signals that you read the Java
7 release notes, not just tutorial sites.
Q17 What is exception propagation? How does it flow through the call stack? [Intermediate]
Answer:
When an exception is thrown and not caught in the current method, it propagates up the call stack to the caller. Each
frame is unwound (finally blocks run) until a matching catch is found or the thread terminates.
For checked exceptions, each intermediate method must either catch or declare (throws) the exception — the compiler
enforces this chain.
For unchecked exceptions, the propagation is silent — no declarations required at each level.
💡 Interviewer Tip:
Interviewers sometimes ask: 'What if there are multiple matching catch blocks?' The first matching catch in
order wins. Always order from most specific to most general.
Answer:
Exception swallowing means catching an exception and doing nothing (or just printing it), allowing the program to
continue as if nothing happened. This is one of the most dangerous Java anti-patterns.
Real consequences: silent data corruption, partially committed transactions, misleading application state.
Java Exception Handling Interview Prep · Page
Acceptable alternatives: log and rethrow, wrap and rethrow, or if you genuinely must ignore (e.g., cleanup), add a
comment explaining why.
// ANTI-PATTERN: Swallowing
try {
[Link]();
} catch (Exception e) {
// TODO fix later ← NEVER do this in production
}
💡 Interviewer Tip:
At a 3-year experience level, interviewers expect you to proactively mention this as something you watch for in
code reviews. It signals maturity.
Answer:
StackOverflowError is thrown when the JVM's call stack runs out of space — almost always due to infinite or deeply
recursive method calls.
Each method call frame consumes stack space. The JVM stack has a fixed size (configurable via -Xss). When exceeded,
SOE is thrown.
Can be caught (it's a Throwable) but you almost never should — there's typically no way to recover meaningfully.
💡 Interviewer Tip:
Follow-up: 'How would you diagnose SOE in production?' Answer: look at the stack trace — all frames will be
the same method repeating. Increase -Xss only as a last resort; fix the recursion.
Q20 What is OutOfMemoryError and what are its common causes? [Intermediate]
Answer:
OutOfMemoryError is thrown when the JVM cannot allocate an object because the heap is exhausted.
Java Exception Handling Interview Prep · Page
Common causes:
→ Memory leaks (objects referenced longer than needed — e.g., static collections growing unboundedly).
→ Heap too small for the workload (-Xmx not set appropriately).
→ Metaspace exhaustion (too many class definitions, common in environments with lots of dynamic class generation).
→ Large object allocation (loading a huge file into a byte[] all at once).
Diagnosed with heap dumps (-XX:+HeapDumpOnOutOfMemoryError) and tools like Eclipse MAT or JVisualVM.
💡 Interviewer Tip:
Interviewers want to hear 'heap dump' and at least one profiling tool. Mentioning -XX:
+HeapDumpOnOutOfMemoryError and that you've actually analyzed one (even if not) shows operational
maturity.
Answer:
If a checked exception is thrown inside a static initializer block, it must be caught within the block — you cannot declare
throws on a static initializer.
If an unchecked exception or error escapes a static initializer, the class fails to load. Any subsequent attempt to use that
class throws ExceptionInInitializerError, wrapping the original exception.
After the first failure, subsequent attempts throw NoClassDefFoundError (because the JVM marks the class as failed).
class Config {
static final Properties props;
static {
try {
props = loadConfig(); // might throw IOException
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
}
// First use: ExceptionInInitializerError
// Later uses: NoClassDefFoundError
💡 Interviewer Tip:
ExceptionInInitializerError → NoClassDefFoundError progression is a subtle but real production issue.
Interviewers at senior level often ask about this to check JVM class-loading knowledge.
Q22 What is the difference between printStackTrace() and using a logger? [Intermediate]
Answer:
printStackTrace() writes to [Link] — synchronously, without any timestamp, log level, or correlation ID. In a multi-
threaded application, output from multiple threads interleaves and becomes unreadable.
// Wrong
} catch (Exception e) {
[Link](); // no level, no context, no correlation
}
// Correct
} catch (Exception e) {
[Link]("Order processing failed for orderId={}", orderId, e);
}
// Output includes: timestamp, level, thread, correlation ID, stack
💡 Interviewer Tip:
This question tests real production experience. Mentioning MDC (Mapped Diagnostic Context) for correlation
IDs, and structured logging (JSON logs for ELK/Splunk), makes you stand out significantly.
Q23 Can a constructor throw an exception? What happens to the object? [Intermediate]
Answer:
Yes, a constructor can throw any exception (checked or unchecked).
If a constructor throws, the object is never fully constructed. The reference is null (or never assigned). The JVM's
garbage collector will clean up any partially allocated object.
Finalizers (deprecated) on a partially constructed object will NOT be called. This was a historical security concern with
finalizer attacks — addressed by sealing constructors.
Best practice: validate arguments early in the constructor and throw IllegalArgumentException fast.
💡 Interviewer Tip:
Follow-up: 'What if a superclass constructor throws — does the subclass constructor run?' No — the exception
propagates immediately, the subclass constructor body never executes.
Q24 What is NullPointerException? How has Java 14+ improved it? [Intermediate]
💡 Interviewer Tip:
Mentioning JEP 358 and Optional demonstrates you stay current with Java evolution. Interviewers at
companies using Java 17+ LTS will appreciate this.
Answer:
1. Be specific — catch the most specific exception type, not Exception or Throwable.
2. Never swallow — always log or rethrow.
3. Preserve the cause — always pass the original exception as the cause when wrapping.
4. Don't use exceptions for flow control — they're expensive and semantically wrong.
5. Prefer unchecked exceptions in modern APIs — reduces boilerplate, aligns with Spring/modern frameworks.
6. Fail fast — validate preconditions early, throw IllegalArgumentException immediately.
7. Use try-with-resources — always, for any AutoCloseable.
8. Document with @throws Javadoc — especially for unchecked exceptions on public APIs.
💡 Interviewer Tip:
At a 3-year level, interviewers expect you to rattle off 5-6 best practices without prompting. 'Exceptions for flow
control is slow' (because stack trace construction is expensive) is a sophisticated point that resonates with
senior engineers.
Must declare with throws? Checked: YES. Unchecked: NO (optional but good practice).
Modern API design trend Prefer unchecked — reduces boilerplate, cleaner APIs.
Q31 Why does Spring use unchecked exceptions? What is DataAccessException? [Advanced]
Answer:
Spring made a deliberate architectural decision to translate all data access exceptions (from JDBC, JPA, Hibernate) into
a hierarchy of unchecked exceptions under DataAccessException.
Reasons:
1. Vendor neutrality — SQLException is database-specific; DataAccessException subtypes are portable.
2. Cleaner code — callers choose whether to handle, without forced try-catch boilerplate.
3. Checked exceptions in large codebases cause 'exception pollution' — every method in the call chain must declare
them.
Rod Johnson (Spring founder) explicitly argued in 'Expert One-on-One J2EE Design and Development' that checked
exceptions are overused and lead to swallowing.
💡 Interviewer Tip:
This is a litmus test question for 3-year candidates. Being able to explain the philosophy (not just 'Spring uses
unchecked') — and mentioning checked exception pollution — is what gets you to the next round.
Answer:
By default, @Transactional rolls back ONLY on unchecked exceptions (RuntimeException and Error). Checked
exceptions do NOT trigger rollback by default.
You can customize with rollbackFor and noRollbackFor attributes.
Critical: If you catch an exception inside a @Transactional method and don't rethrow it, Spring never sees the exception
and will COMMIT the transaction — potentially corrupting data.
@Transactional
public void placeOrder(Order order) {
[Link](order);
try {
[Link](order);
} catch (PaymentException e) {
[Link]("Payment failed", e);
// DANGER: transaction will COMMIT! order saved, no payment
// FIX: rethrow or call
[Link]().setRollbackOnly()
}
}
@Transactional(rollbackFor = [Link])
public void safePlace(Order order) throws CheckedPaymentException { }
💡 Interviewer Tip:
This is the most common @Transactional bug in production Spring apps. Interviewers at companies using
Spring Boot expect you to know this cold. Mention
[Link]().setRollbackOnly() as a last resort.
Q33 How do you implement global exception handling in Spring Boot? [Advanced]
Answer:
@ControllerAdvice / @RestControllerAdvice: A class annotated with this acts as a global exception handler for all
controllers. Methods annotated with @ExceptionHandler catch specific exception types.
This lets you centralize error response formatting — returning structured JSON (error code, message, timestamp)
instead of Spring's default whitepage error.
You can also implement ResponseEntityExceptionHandler to override Spring MVC's built-in exception handling (e.g.,
MethodArgumentNotValidException for validation).
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler([Link])
public ResponseEntity<ErrorResponse> handleOrderNotFound(
OrderNotFoundException ex, WebRequest request) {
ErrorResponse body = new ErrorResponse(
💡 Interviewer Tip:
Full marks answer: mention ProblemDetail (RFC 9457) introduced in Spring 6 / Spring Boot 3 as the
standardized error response format. Shows you're tracking modern Spring.
Answer:
@ExceptionHandler inside a @Controller class: applies only to that specific controller. Useful for controller-specific
errors.
@ControllerAdvice: applies globally across all controllers. You can scope it by package, annotation, or class using
basePackages / annotations / assignableTypes.
Resolution order: local @ExceptionHandler in the controller takes priority over global @ControllerAdvice.
@RestController
public class OrderController {
// Only handles exceptions from THIS controller
@ExceptionHandler([Link])
public ResponseEntity<?> handleLocal(OrderNotFoundException e) { ... }
}
💡 Interviewer Tip:
Interviewers ask: 'What if both a local handler and a global handler can handle the same exception?' Local
handler wins — it's more specific.
Q35 How do exceptions interact with Java streams and lambdas? [Advanced]
Answer:
Lambdas used in streams (Predicate, Function, Consumer) cannot throw checked exceptions — the functional interface
signature doesn't declare them.
Workarounds:
💡 Interviewer Tip:
This reveals real-world Lambda experience. UncheckedIOException (built into JDK) is the 'correct' wrapper for
IOException in streams. Mentioning @SneakyThrows (and its controversy) is a great bonus.
Answer:
Thrown when an unchecked exception escapes a static initializer block. It wraps the original exception.
After this, every subsequent attempt to reference the class throws NoClassDefFoundError (not EIIE again).
Diagnosed by calling getCause() on the ExceptionInInitializerError to find the root problem.
class AppConfig {
static final String DB_URL = loadFromVault(); // throws RuntimeException
static String loadFromVault() { throw new RuntimeException("vault down"); }
}
// First access:
// [Link]: vault down
💡 Interviewer Tip:
The key thing to mention: getCause() reveals the actual problem. NCDFE on subsequent accesses is a common
source of confusion in production diagnostics.
Answer:
UncheckedIOException (Java 8+) is an unchecked wrapper for IOException. It's provided by the JDK specifically for use
in contexts that cannot throw checked exceptions — streams, lambdas, method references.
Use it instead of a raw RuntimeException wrapper because: it preserves semantic meaning (this was an IO problem),
getCause() returns IOException directly, and catch (UncheckedIOException) lets callers selectively handle IO failures
without catching all RuntimeExceptions.
💡 Interviewer Tip:
Most candidates just say 'wrap in RuntimeException'. Knowing UncheckedIOException exists (and why it's
better) is a distinguishing answer.
Answer:
@Async methods return CompletableFuture. Exceptions thrown inside are not propagated to the caller's thread —
they're stored in the future and only surface when you call .get().
CompletableFuture wraps exceptions in CompletionException (for thenApply etc.) or ExecutionException (for .get()).
For @Async, implement AsyncUncaughtExceptionHandler to globally handle exceptions from void @Async methods.
For CompletableFuture, use .exceptionally() or .handle() to react to failures without blocking.
💡 Interviewer Tip:
This is a strong differentiator question. CompletionException vs ExecutionException distinction (thenApply
wraps in CompletionException; .get() wraps in ExecutionException) is the kind of detail that shows real async
experience.
What is the difference between fail-fast and fail-safe iterators in terms of exceptions?
Q39 [Advanced]
Answer:
Fail-fast iterators (ArrayList, HashMap): throw ConcurrentModificationException immediately if the collection is
structurally modified during iteration (outside the iterator itself). They check an internal modCount.
Fail-safe iterators (CopyOnWriteArrayList, ConcurrentHashMap): iterate over a snapshot/clone and never throw
ConcurrentModificationException — but they may not reflect recent modifications.
Neither guarantees thread safety in the general sense; fail-safe just avoids the exception by trading memory (copy) for
consistency.
// Fix 1: [Link]()
Iterator<String> it = [Link]();
while ([Link]()) {
if ([Link]().equals("b")) [Link](); // safe
}
💡 Interviewer Tip:
ConcurrentModificationException is not just a threading issue — it happens on a single thread if you modify
during enhanced-for iteration. This distinction is commonly missed.
Q40 What is the cost of exceptions in Java? Should you use them for control flow? [Advanced]
Answer:
💡 Interviewer Tip:
Mentioning fillInStackTrace() override is an advanced technique that very few candidates know. It's used in
frameworks like Netty for internal flow control. It signals you understand JVM performance.
What exception is thrown when you access an invalid array index, null, or divide by
Q41 zero? [Basic]
Answer:
ArrayIndexOutOfBoundsException: accessing arr[-1] or arr[[Link]].
NegativeArraySizeException: creating an array with negative size: new int[-1].
NullPointerException: dereferencing a null reference.
ArithmeticException: integer division by zero (int a = 5/0). Note: floating point division by zero returns Infinity, not an
exception.
ClassCastException: invalid cast: (String) new Object().
These are all unchecked (RuntimeException subclasses).
String s = null;
[Link](); // NullPointerException
💡 Interviewer Tip:
Floating-point division by zero returning Infinity (not an exception) trips up many candidates. Always worth
mentioning.
Q42 How does exception handling work with Java generics (type erasure)? [Advanced]
Answer:
You CANNOT create a generic exception class: 'class MyException<T> extends Exception' will not compile because you
cannot catch a generic type (type erasure means the JVM can't distinguish MyException<String> from
MyException<Integer> at catch time).
You CAN use generics in methods that throw exceptions — the @SuppressWarnings("unchecked") trick or bounded
wildcards can help.
You CANNOT use a type parameter in a catch clause: catch (T e) is illegal.
💡 Interviewer Tip:
This is a niche but high-signal question that tests both generics and exception knowledge simultaneously. The
reason generic exceptions are disallowed is purely due to type erasure — the bytecode catch table uses raw
types.
What is try-with-resources with multiple resources and what order do they close?
Q43 [Advanced]
Answer:
When multiple resources are declared in try-with-resources, they close in REVERSE order of declaration.
This mirrors the natural stack-based cleanup order — the last resource opened is the first to close, since later resources
may depend on earlier ones.
💡 Interviewer Tip:
Reverse close order is a frequently tested detail. The suppression behavior for multiple close failures is a rarer
but impressive addition to the answer.
How does exception handling differ between Thread and Executor/thread pool?
Q44 [Advanced]
Answer:
In a plain Thread, you can set an UncaughtExceptionHandler to capture unchecked exceptions that escape the run()
method.
In ExecutorService (thread pool), exceptions from submitted Runnables are silently swallowed unless you call
[Link](). Exceptions from Callables are wrapped in ExecutionException and surface on .get().
This silent swallowing in ExecutorService is a notorious source of bugs — tasks fail with no visible error.
// Plain Thread
Thread t = new Thread(riskyTask);
[Link]((thread, ex) ->
[Link]("Thread {} failed", [Link](), ex));
[Link]();
💡 Interviewer Tip:
Silent exception swallowing in thread pools is a real production bug pattern. Interviewers from companies with
high concurrency requirements (fintech, e-commerce) often ask this specifically.
Q45 What is the difference between initCause() and the constructor cause parameter?
Answer:
Both set the cause of an exception, but they differ in usability and constraints:
Constructor: new RuntimeException(message, cause) — preferred, sets cause at creation time, immutable after.
initCause(): can only be called ONCE, and only if the cause was not already set in the constructor. Throws
IllegalStateException on second call.
initCause() exists for pre-Java-1.4 exceptions that didn't have cause-aware constructors (e.g., some legacy APIs).
// Dangerous double-init
RuntimeException e = new RuntimeException("msg", cause1);
[Link](cause2); // IllegalStateException — cause already set
💡 Interviewer Tip:
The one-time-only constraint on initCause() is a classic trick question. The safe answer: always prefer the
constructor form.
Q46 How do you handle exceptions in Spring Batch or scheduled jobs? [Advanced]
Answer:
In Spring Batch, exceptions can be configured to trigger retry, skip, or step failure at the StepBuilder level.
For @Scheduled methods, exceptions are caught by the task scheduler. By default they're logged and the next
execution proceeds. You should implement ErrorHandler or AsyncUncaughtExceptionHandler.
Best pattern: wrap the scheduled job body in try-catch, emit metrics (Micrometer), and alert on repeated failures.
@Scheduled(fixedDelay = 60_000)
public void runJob() {
try {
[Link](importJob, new JobParameters());
} catch (JobExecutionException e) {
[Link]("Scheduled job failed", e);
[Link]("[Link]").increment();
[Link]("Job failed: " + [Link]());
}
}
What happens when you call getMessage(), getCause(), and toString() on an exception?
Q47 [Advanced]
Answer:
getMessage(): returns the detail message string passed to the constructor. May be null.
getLocalizedMessage(): by default same as getMessage(), but subclasses can override for locale-specific messages.
getCause(): returns the Throwable that caused this exception, or null if none.
toString(): returns className + ': ' + message (e.g., [Link]: oops).
printStackTrace(): prints toString() then the stack trace to [Link], including any chained causes.
💡 Interviewer Tip:
A quick-fire question to test basic Throwable API knowledge. Knowing getLocalizedMessage() and that
toString() is what [Link](exception) calls will show depth.
Q48 How do you write unit tests for exception handling? [Advanced]
Answer:
JUnit 5: use assertThrows() to verify that specific code throws a specific exception type. You can then assert on the
exception message, cause, or custom fields.
assertDoesNotThrow() verifies no exception is thrown.
Mockito: use thenThrow() to simulate exception scenarios from dependencies.
Best practice: test both the exception type AND the message/cause to ensure useful error messages reach callers.
// JUnit 5
@Test
void shouldThrowWhenOrderNotFound() {
💡 Interviewer Tip:
The assertThrows + field assertion pattern is expected at a 3-year level. Testing that the cause is correctly
chained ([Link]() instanceof ...) shows you care about debuggability.
Q49 What is the role of exception handling in microservices (REST APIs)? [Advanced]
Answer:
In microservices, exceptions must be translated into meaningful HTTP responses with standard error formats. Key
concerns:
1. Error response structure: use ProblemDetail (RFC 9457 / Spring 6) or a custom ErrorResponse DTO.
2. HTTP status mapping: 404 for not found, 400 for validation errors, 409 for conflicts, 500 for unexpected.
3. Don't expose internals: never return stack traces, SQL errors, or internal class names to API consumers.
4. Correlation IDs: include a trace/correlation ID in error responses so clients can report issues.
5. Circuit breaker exceptions: handle Resilience4j's CallNotPermittedException for degraded-mode responses.
💡 Interviewer Tip:
ProblemDetail RFC 9457 support in Spring Boot 3 is the current industry standard. Mentioning never exposing
stack traces to external clients (security concern, information leakage) is essential.
Answer:
This open-ended question tests practical experience. Strong answers follow the STAR pattern (Situation, Task, Action,
Result) and demonstrate real debugging skills.
Example scenario: A @Transactional service method caught an exception internally to log it, without rethrowing. The
transaction committed despite a failed downstream call, leaving the database in an inconsistent state.
Investigation: Spring's transaction interceptor never saw the exception, so rollback was never triggered.
Fix: Rethrow a RuntimeException (or call setRollbackOnly()), or restructure to not catch inside the transactional
boundary.
Lesson: Always be aware of where your @Transactional boundary is relative to your try-catch blocks.
// The bug
@Transactional
public void processOrder(Order order) {
[Link](order);
try {
[Link](order); // throws!
} catch (Exception e) {
[Link]("inventory failed", e); // swallowed → commit!
}
}
// The fix
@Transactional
public void processOrder(Order order) {
[Link](order);
try {
[Link](order);
} catch (Exception e) {
[Link]("inventory failed", e);
throw new OrderProcessingException("Inventory reservation failed", e);
}
}
💡 Interviewer Tip:
Interviewers use this question to gauge real experience vs theoretical knowledge. A crisp STAR answer about
@Transactional + exception swallowing, or a CompletableFuture silent failure, immediately signals production-
level thinking.