Exception Guidelines
Contingencies and Faults
The following concepts of contingency and fault is derived from Barry Ruzek's Article ''Effective Java Exceptions'' and it is advised that developers atleast skim it.
Grossly oversimplified picture in a nutshell: A contingency is a control-flow related checked exception. A fault is an unexpected RuntimeException or Error that does not relate to the workflow of the class/method/application. Faults subclassing Error are generally signs that the entire JVM might be compromised (see also javadoc for the Error class) and should as a rule of thumb not be caught (except possibly by the outermost fault barrier).
The following table is copied from Ruzek's article:
Condition |
Contingency |
Fault |
Is considered to be |
A part of the design |
A nasty surprise |
Is expected to happen |
Regularly but rarely |
Never |
Who cares about it |
The upstream code that invokes the method |
The people who need to fix the problem |
Examples |
Alternative return modes |
Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers |
Best Mapping |
A checked exception |
An unchecked exception |
A fault barrier is a conceptual layer in the control flow where certain kinds of faults are caught. It is a perfectly acceptable fault barrier to catch general Exceptions, used for example to encapsulate unreliable subsystems. It must be specified in the javadoc of a method that it acts as a fault barrier and what the intended purpose of the fault handling is (including the types of faults it handles).
Best Practices
A fault is a RuntimeException or in rare cases an Error
- It is allowed to wrap a contingency in a fault and send it up stream
- A contingency cannot pass a fault barrier although a fault barrier can have contingencies it self.
- A fault barrier cannot let a fault pass that is one of the fault types that it is itself designated to handle.
- A contingency is specific to one problem or event in the control flow.
- Avoid that the same contingency can be thrown by two or more different causes in the same method. If this is the case you should either refactor your method or create different types of exceptions for the other contingencies.
- Never have a finally block in a fault barrier - it may throw exceptions that escape the fault barrier
Threads
- Every thread should have a fault barrier at the top. In practice this means that we put try/catch as the outermost statement in each run method
Gotcha: Beware of implicitly started threads: Webservice-initiated methods, threads started by events like JMS messages or Timer events.
Gotcha: Beware that throwing a nasty fault might only stop the running thread and not the application as a whole
NOTE: To enforce that we minimally log exceptions at the root of a thread we could use Thread.setDefaultUncaughtExceptionHandler.
Finally
Close your resources and files in a finally clause
Never ever do return statements in a finally clause
System.Exit
Only in the main method. Use faults instead, for really nasty faults use Error subclasses
Gotcha: Beware that throwing a nasty fault might only stop the running thread and not the application as a whole
Throwing Exceptions
Generally don't log when throwing exceptions. Instead make sure to put a good error message in the exception. Good error messages include as much context as reasonable. For example
for (Record rec : database) { if (rec.length == 0) { throw new EmptyRecordException ("Record " + rec.getId() + " was empty"); } ... }
Rethrowing Exceptions
Rethrowing exceptions is fine, as long as the old Exception is wrapped in a new Exception as this preserves the stack trace. Follow the guidelines from the throwing exceptions chapter above.
try { myStream.write(rec.toBytes()); catch (IOException e) { throw new RecordWriterException("Could not store the record '" + rec.getId() + "' to file '" + filename + "'", e); }