Question 20 · Section 7

Why you should not swallow exceptions (catch empty)?

It's catching an exception without any action:

Language versions: English Russian Ukrainian

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:

  1. JVM creates an exception object (this already cost CPU cycles for fillInStackTrace())
  2. Enters the catch block - there’s nothing there
  3. Continues execution after catch - exception object becomes unreachable and will be collected by GC
  4. 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

  1. 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
  2. In transactions - swallowed SQLException leads to partial data commit
  3. In stream API - null in collection instead of error silently breaks downstream
  4. In async code - CompletableFuture with swallowed exception never completes exceptionally
  5. In middleware - swallowed exception returns client 200 OK instead 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 catch still wastes CPU on fillInStackTrace() - “silent” exception is not free
  • Acceptable scenarios: closing resources (before Java 7), InterruptedException with flag restoration, optional cache
  • Minimum - log.debug() even on conscious ignoring
  • In transactions, swallowed SQLException = partial data commit
  • In async code, swallowed exception = CompletableFuture never 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