5. “Recovering from exceptions” should be done rarely, if at all
Most code shouldn’t try and recover from exceptions. If you can’t prove that business outputs will be 100% correct, it’s generally best just to fail.
There are a couple of situations where exceptions can correctly be caught and handled.
1. Parsing — when a missing/ invalid input can be ignored.
2. “Business contingency” — exceptions indicating a business outcome other than success; such as InsufficientFundsException. These should result in a different process/ execution flow.
High-reliability software may also attempt to skip or retry failed subsystems, in some select situations:
3. Optional subsystems — eg. a printer, which might not be required right now to complete the business process.
4. Retry unreliable subsystems — retry may be used for unreliable systems/ connections. It is also common when obtaining locks against concurrent (multi-user) databases.
Retry & fault-recovery code is complex, and creates the possibility of pathological interactions (flooding, loops, log flooding, overloading a failing subsystem). This creates it’s own potential for error — I have seen Oracle crash due to faults in “recovery” code.
Generally, recovery or retry code should be used only where there is a definite business need.
6. Containers, but not threads, provide the outermost exception handler for you
Because the requirement to report errors & log stacktraces is so common and basic, most EJB/ web containers & frameworks (Spring, Struts, Swing/ AWT) provide a standard error-handler for you.
These typically return a basic error response & log the exception. Some allow customization, to show a nicer looking error-page.
However, Threads and threaded code (SwingWorker, ExecutorPool etc) do not have nice exception-handlers builtin. Uncaught exceptions thrown in these will typically result in silent failure, which is time-consuming to debug.
For standard web applications & Swing, you can rely on the standard exception-handlers without needing to write any code. If you’re using threads, however, make sure your exceptions will be handled.
7. Prefer runtime to checked exceptions
Modern Java use and other languages (C#) have moved away from the “checked exception” concept, to strongly prefer runtime (unchecked) exceptions.
Checked exceptions were originally intended for “contingency outcomes” — predictable business results, other than success. The classical example is InsufficientFundsException when attempting to pay or transfer money.
These are useful for specific individual situations, for which an alternate business process & response may exist.
However, they were never conceived to include unpredictable low-level & infrastructure failures — which can occur anywhere in code, at any time, and for which no effective business alternates exist.
“Potential to fail anytime, anywhere” due to underlying causes is simply not meaningful to deal with in a specific way. Runtime exceptions were designed to be unchecked, to allow unpredictable failure to be thrown from anywhere & handled further out. This is much better suited & allows generalized exception handling strategies such as “fail & report”, “retry” or “skip optional step”.
The best approach to dealing with checked exceptions (not representing genuine alternates/ or business contingencies) is to wrap and rethrow using a suitable unchecked exception.
Often, it is most convenient to wrap the entire method body in a catch-block to rethrow checked exceptions.