Exception Handling in Java: Best Practices and Common Mistakes - Quiz
Total: 5 questions
1. What is exception swallowing in Java and why is it dangerous?
What is exception swallowing in Java and why is it dangerous?
Exception swallowing means catching an exception without reacting to it: an empty catch (often with a // TODO comment), a catch with only e.printStackTrace(), or replacing the error with null or -1. Such code compiles without warnings, but the program keeps running in a wrong state, and the real cause of the failure surfaces later and somewhere else.
- Empty catch: the error turns into silence — for example, a loop that saves orders reports «no errors» although one of three orders was never saved.
- Only printStackTrace(): execution continues (the customer gets a confirmation for an order that was not saved), and the output goes to
System.err, bypassing the logger — no timestamp, level or context, and on a server it is often lost. - return null or -1: «no data» and «storage unavailable» become indistinguishable; the caller gets a
NullPointerExceptionlater, and-1can be a valid value. Express a missing result withOptionaland a failure with an exception.
The fix: do not catch an exception where there is nothing to do with it; if the failure of one item must be tolerated, record it (collect it into a report or log it together with the exception object). If ignoring is intentional, name the parameter ignored and explain why in a comment; in Java 22+ you can write catch (NumberFormatException _).
2. Why should you not catch Exception or Throwable, and where in a program is catch (Exception e) acceptable?
Why should you not catch Exception or Throwable, and where in a program is catch (Exception e) acceptable?
A broad catch catches not only the error you expected, but everything else too:
catch (Exception e)catches everyRuntimeException— bugs such asNullPointerException,ClassCastException,IndexOutOfBoundsException. With such a block,parseOrDefault(null, 0)returns the default value instead of failing, and a programmer mistake is disguised as «a string in the wrong format». It also catchesInterruptedException, so the thread loses its interrupt flag.catch (Throwable t)addsOutOfMemoryErrorandStackOverflowError; after them the state of the program is not guaranteed, and carrying on is dangerous.
The rule: catch the most specific type you know how to handle (NumberFormatException instead of Exception), and list several expected types with multi-catch.
catch (Exception e) is fine at the application boundary — the topmost place where one failed operation must not stop the others: the main method, an HTTP request handler, the loop of a worker thread processing a task queue. There the exception is logged with its stack trace, and the program moves on to the next task or returns a 500 error to the client. In Spring such a boundary is usually a single @ControllerAdvice. Catching Throwable is acceptable only at the top level of a thread or framework to log the error, after which an Error is rethrown.
3. What is the log-and-rethrow anti-pattern, and how should you log exceptions correctly?
What is the log-and-rethrow anti-pattern, and how should you log exceptions correctly?
Log-and-rethrow is when every layer (repository, service, controller) writes the exception to the log and rethrows it. One error produces several ERROR/SEVERE entries and several identical stack traces: monitoring counts several incidents, and it is hard to tell from the log where the error first occurred.
How to do it right:
- Log once — where the exception is finally handled (usually the application boundary). If a
catchblock containsthrow, the method does not handle the error, and a log call before it is almost always redundant. - An intermediate layer either leaves the exception alone or wraps it in an exception of its own layer, with context and the cause:
throw new OrderStorageException("Could not save order id=" + id, e). Without the second argument theCaused bychain is broken. - Pass the exception object to the logger, not
e.getMessage():log.log(Level.SEVERE, "Could not save order id=" + orderId, e)injava.util.loggingorlog.error("... id={}", orderId, e)in SLF4J. The message has no stack trace or causes, and fornew IllegalStateException()it is evennull. - Write context: what you tried to do and with which data, without passwords, card numbers or tokens. Do not use
printStackTrace()orSystem.outinstead of a logger.
4. Checked or unchecked exception for your own API: how do you choose, and why do Spring and other frameworks choose unchecked?
Checked or unchecked exception for your own API: how do you choose, and why do Spring and other frameworks choose unchecked?
The criterion is whether the calling code can do something sensible other than abort the operation. Checked exceptions suit expected external situations the caller can react to: a file is not found, a payment is declined by the bank, a service does not respond in time. Unchecked exceptions suit programmer mistakes and contract violations: a null or invalid argument, a call at the wrong time. A practical test: if almost every caller writes catch (XException e) { throw new RuntimeException(e); }, the exception should have been unchecked.
Why frameworks choose unchecked:
- Checked exceptions scale poorly across layers: an
SQLExceptionfrom a repository requiresthrowsin the service and the controller, JDBC details leak through the whole application, and the forcedcatchblocks often turn into swallowing. That is why Spring translatesSQLExceptioninto the uncheckedDataAccessExceptionhierarchy, and JPA and Hibernate throw the uncheckedPersistenceException. - Lambdas and the Stream API:
Function,Consumer,ToIntFunctiondeclare nothrows, so a checked exception has to be wrapped inside every lambda (forIOExceptionthere isUncheckedIOException). - @Transactional by default rolls back only on
RuntimeExceptionandError; on a checked exception the data is committed unless you specifyrollbackFor = Exception.class— a common trick interview question.
Kotlin, Scala and C# have no checked exceptions at all.
5. How should you handle InterruptedException, and why must its catch block never be left empty?
How should you handle InterruptedException, and why must its catch block never be left empty?
InterruptedException is a checked exception meaning that another thread asked the current one to stop (called interrupt()). When Thread.sleep(), wait() or BlockingQueue.take() throw it, they clear the interrupt flag. If the flag is set before the call, sleep() throws the exception immediately.
So an empty catch erases the request to stop without a trace: a while (!Thread.currentThread().isInterrupted()) loop in a worker keeps spinning, and ExecutorService.shutdownNow(), which stops tasks precisely through interrupt(), cannot shut the pool down.
Correct options:
- propagate the exception with
throws InterruptedException; - if you cannot propagate it (for example, in
Runnable.run()), restore the flag:catch (InterruptedException e) { Thread.currentThread().interrupt(); }— after thatisInterrupted()returnstrueagain.
A catch (Exception e) around code with sleep() or take() has the same effect as an empty catch: it needs a separate catch (InterruptedException e) before it that restores the flag.