Why you should not swallow exceptions (catch empty)?
It's catching an exception without any action:
Junior Level
What is “swallowing” exceptions
It’s catching an exception without any action:
try {
processOrder(order);
} catch (Exception e) {
// Empty - exception is "swallowed"
}
// Program continues as if nothing happened
Mechanism: why exception is lost
When JVM executes a catch block, it removes the exception from the error handling stack. If the catch block is empty:
- JVM creates an exception object (this already cost CPU cycles for
fillInStackTrace()) - Enters the
catchblock - there’s nothing there - Continues execution after
catch- exception object becomes unreachable and will be collected by GC - No code above in the stack knows about the error - the method that called
processOrder()thinks everything went fine
It’s like a doorbell you heard but decided not to open - the visitor left, and you never even knew who it was.
Why it’s bad
When an exception is swallowed, the program enters an inconsistent state:
try {
connection.beginTransaction();
connection.save(order); // SQLException!
connection.commit(); // Will execute - partial data lost
} catch (SQLException e) {
// Empty - transaction commits with incomplete data
}
Result: some data written, some not. Business logic broken. In logs - silence.
What to do instead
// Minimum - log it
try {
processOrder(order);
} catch (Exception e) {
log.error("Failed to process order", e);
}
// Better - rethrow
try {
processOrder(order);
} catch (Exception e) {
log.error("Failed to process order", e);
throw new OrderProcessingException("Order failed", e);
}
Middle Level
When it’s ACCEPTABLE
Only a few scenarios where empty (or nearly empty) catch block is OK:
1. Closing resources (before Java 7):
} finally {
try { if (socket != null) socket.close(); }
catch (IOException ignored) { /* Nothing can be done - socket will close anyway */ }
}
Modern solution: try-with-resources (it handles suppressed exceptions itself).
2. Expected interruptions on shutdown:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Restore interrupt status - required!
Thread.currentThread().interrupt();
}
Here catch is not empty - we restore the interrupted flag.
3. Ignorable optional operations:
// Caching - if cache unavailable, compute directly
try {
cache.put(key, value);
} catch (CacheException e) {
// Cache is an optimization, not a requirement. Its unavailability is not a business error.
// Minimum - debug log:
log.debug("Cache unavailable, skipping", e);
}
4. Cleanup on shutdown:
try {
temporaryFile.delete();
} catch (IOException e) {
// File is temporary, OS will clean /tmp on reboot anyway
log.debug("Failed to delete temp file {}", temporaryFile, e);
}
Key principle: if you ignore an exception, make sure:
- It’s a conscious decision, not laziness
- Error consequences don’t affect business logic
- There’s at least a
log.debug()for debugging
When NOT to use empty catch
- Business logic - almost never. Even
log.debug()is better than emptiness, because it gives a thread for debugging. The only exception: if business specification explicitly says “ignore this error” - but still log at DEBUG - In transactions - swallowed
SQLExceptionleads to partial data commit - In stream API -
nullin collection instead of error silently breaks downstream - In async code -
CompletableFuturewith swallowed exception never completes exceptionally - In middleware - swallowed exception returns client
200 OKinstead of error
Hidden Latency
Swallowing exceptions hides performance problems. Service crashes on timeout, errors are swallowed - you just see slow operation without understanding the causes.
CPU Spikes
Frequent occurrence and swallowing of exceptions still loads CPU on fillInStackTrace(). “Silent” exception is not “free”.
Functional Streams
Swallowing inside forEach or map - collection with incomplete data without warning:
list.stream()
.map(item -> {
try {
return process(item);
} catch (Exception e) {
// Swallowed - result null
return null;
}
})
.collect(Collectors.toList()); // [result1, null, result3, null]
Global Exception Handler
If you swallow an exception in the middle of the chain, @ControllerAdvice in Spring returns 200 OK to the client - API deception.
Senior Level
Quiet Failure and zombie systems
A swallowed exception turns the system into a “zombie” - it works, but incorrectly. Without self-healing mechanism, this is a catastrophe.
Static Analysis
SonarQube rules (“Exceptions should not be ignored”) and IntelliJ Inspections (“Empty catch block”) should be blocking in CI/CD.
Log at Least as DEBUG
If an exception can be ignored - log it at least at DEBUG level:
try {
optionalResource.close();
} catch (IOException e) {
log.debug("Failed to close optional resource", e);
}
This will save hours of debugging.
Self-Healing Systems
If you ignore an error - there should be a self-healing mechanism:
try {
cache.put(key, value);
} catch (CacheException e) {
log.warn("Cache unavailable, falling back to direct computation", e);
return computeDirectly(); // Self-healing
}
Metric-driven detection
try {
processOrder(order);
} catch (Exception e) {
meterRegistry.counter("exceptions.swallowed", "type", e.getClass().getSimpleName()).increment();
log.debug("Swallowed exception", e);
}
Diagnostics
- SonarQube - blocks empty catch blocks
- IntelliJ Inspections - highlights
catch (Exception e) {} - Prometheus/Grafana - alerting on anomalies in swallowed exceptions
- Thread Dumps - if thread “hangs” after swallowed exception
Interview Cheat Sheet
Must know:
- Swallowing exceptions puts program into inconsistent state without log entry
- Empty
catchstill wastes CPU onfillInStackTrace()- “silent” exception is not free - Acceptable scenarios: closing resources (before Java 7),
InterruptedExceptionwith flag restoration, optional cache - Minimum -
log.debug()even on conscious ignoring - In transactions, swallowed
SQLException= partial data commit - In async code, swallowed exception =
CompletableFuturenever completes exceptionally - SonarQube and IntelliJ Inspections should block empty catch blocks in CI/CD
Frequent follow-up questions:
- When CAN you swallow an exception? - Optional operations (cache), cleanup on shutdown, but minimum with
log.debug() - What to do with
InterruptedException? - Restore flag:Thread.currentThread().interrupt() - Why is it catastrophic in transactions? - Swallowed error = partial commit, data corrupted
- How to detect the problem in production? - SonarQube rules, Prometheus swallowed exceptions metrics, thread dumps
Red flags (NOT to say):
- “I have empty catch blocks everywhere if error is not critical” - this is a zombie system
- “I swallow exceptions in business logic” - never acceptable
- “Empty catch doesn’t affect performance” -
fillInStackTrace()costs CPU cycles - “I don’t need logs, I know the error happens” - without logs there’s no debugging
Related topics:
- [[17. How to properly log exceptions]] - logging at least at DEBUG
- [[23. What are suppressed exceptions]] - TWR preserves both errors
- [[22. What happens if an exception also occurs in the finally block]] - error loss in finally
- [[24. Can you have multiple catch blocks for one try]] - proper handling of different types