Exception handling in Java is crucial for reliability, but a common source of bloat & prone to miscoding. However, dealing with exceptions correctly can be surprisingly easy.
We cover 10 “foundation” best practices to simplify your coding & help you handle exceptions correctly.
1. Prefer ‘throw’ to ‘catch’
Principle here is that, in general, most exceptions are unrecoverable from your code. Your code does not (should not) have the ability, or specific knowledge, to repair the network or restart the database. That would violate encapsulation.
Given that, the responsibility of your code is to fail immediately & safely, and report an error. The critical danger to avoid is allowing code to continue with incorrect/ erroneous data, and save or output false results to the business level.
2. Categorize exceptions by cause
Since exceptions come with a stacktrace, it is more useful to categorize them by subsystem causing failure, rather than by which application module the failure occurred in.
For example, categorizing exceptions into SQL, file access, or configuration types is generally far more useful than separate types for Customer, Account, and Order modules.
Keep the heirarchy simple. For the vast majority of applications, the application module which failure occurred in is irrelevant — no practical difference in handling or reporting would be made.
It is useful, however, to distinguish major underlying causes of failure/ and different degrees of recoverability.
3. Keep your exception heirarchy simple
A simple hierarchy is easy for developers to use & throw, making it obvious to find “the right exception”. It should offer basic broad categories for diagnosis & handling.
Overly complicated hierarchies, non-obvious naming, or per-module exceptions leave developers scratching their head looking round for what to use. (This is why people end up using RuntimeException). It shouldn’t be that hard.
Library code which is genuinely separate from the application body may deserve it’s own exceptions, but don’t go overboard.
A clean & effective hierarchy may look like this:
- FailureException extends RuntimeException
- AppSQLException
- AppFileException
- AppConfigException
- AppDataException
- AppInternalException
- RecoverableException extends RuntimeException
- UserException
- ValidationException
In this hierarchy, exceptions a developer needs to rethrow will be under an “App*” exception classname. This makes it easy for developers to find & throw the correct type.
4. Catch at the outermost level
Exceptions must be reported to the business/ external world — and this is done by returning a 500 “error response” or displaying an error in the UI.
Since such an ‘catch’ block or exception-handler requires access to the outside to respond, it means exceptions must be caught & handled at the outermost level.
Simultaneously, the final handler should log the exception & full stacktrace for investigation.
‘Catch’ blocks in internal code should be avoided and their use minimized as they interfere with the reliable propagation of exceptions to the outermost handler.
“However, [check exceptions] 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.”
That’s a darn interesting statement.
Out of curiosity, what is your source for that? Who conceived them?
Reason I wonder is, the JDK itself is horribly cluttered with all these ridiculous checked-exceptions that a client virtually never want to catch — and more keep coming with every new release…
So if your stmt is correct, it means the people who decide on the JDK have completely misunderstood what checked-exceptions were intended for?
Yes, this was the original intent of checked exceptions — a language mechanism to identify possible contingencies and ensure they were handled.
This worked great for reporting predictable non-success outcomes back to callers.
The trouble is that it does not make sense at all for unpredictable low-level failures.. which can effectively happen anywhere, anytime, and are impossible for application code to recover from. Forcing these to be declared is essentially meaningless, since the declarations are needed everywhere.
This understanding is a synthesis of a range of sources. Barry Ruzek probably expresses this best, but here are a few:
– Barry Ruzek, Oracle: Effective Java Exceptions pg 2
– Smartics: Faults and Contingency Exceptions
– Expert One-on-One J2EE Design and Development: Interview with Rod Johnson
Great effort and a really good site Tom. I’ve been enjoying reading these articles and have learned a lot from them already.
Thanks Peter, great to hear your response! Glad you liked it.
Good explanation tom, i got more detail about catch in java. i will share my friend also those details.but i want more example to given. am waiting your given example and explanation. thanks
Why have AppSqlException, AppFileException and …, Considering there already is SqlException, FileException, …
Isn’t it better to have a generic AppException?
About No3 (Keep your exception heirarchy simple) isn’t it best for AppSQLException, AppFileException, …. (and all other internal Exceptions) to be extended from our own AppException?
This way we can treat AppExceptions and RuntimeExceptions differently. So if an AppException is thrown we can be sure that the Application is still in the right state and keep going but when a RuntimeException is thrown we can consider it fatal and let the Application go down.
AppSqlException and AppConfigException might be better examples — most apps use both SQL and Configuration, for example, but somewhat fewer combine SQL with file management.
Anyway, the point is that 1) these are runtime exceptions, and 2) they can inherit from a common type.
Here I’m proposing an application-level FailureException. In the past (as you suggest), I’ve also seen an AppException used — however that design didn’t include the distinction between failures & recoverable exceptions.
Regarding recovery: your thinking is wrong. In general, we can’t recover. Sometimes we can retry (with added cost & complexity). But our primary goal is to fail cleanly.
In the very large part, our code should just aim to fail cleanly. We should never assume that a given exception type lets us globally ignore failed business logic, as that means our application will be running in an incorrect state & worse, persist that wrongness to the DB.
Thanks for your comments, glad you appreciated the article!
Hi Tom,
I read your post “Checked exceptions: Java’s biggest mistake”. I had a similar post: http://tri-katch.blogspot.com/2016/02/java-checked-exception-is-bad.html.
I have been thinking about redesigning exception mechanism (not only limited to Java) for 9+ years (http://www.theserverside.com/news/thread.tss?thread_id=43820#225361). And here is the result: http://tri-katch.blogspot.com/2015/05/catch-code-proposal-to-expand-catch-in.html
Could you take a look and comment?
Thanks.
Bo
Hi Bo, thanks for your interest & comments! I’ve looked at your proposal, but it seems to suggest using strings/ fault codes as a kind of replacement for exception subtypes.
My emphasis is against checked exceptions in general; but specifically, I believe that exception-handling & genuine recovery are relatively rare in code.
These are the circumstances where a ‘catch by type’ is appropriate and I find the combination of code locality & Java exception types to be sufficient and appropriate for that purpose. So, I don’t support special language support to catch by string/ or fault-code.
Thanks for your interesting suggestion however!
Regards,
Tom
Thanks for your great article that summarizes most of my personal practices from 20+ years of Java development.
There’s only one statement I’m missing: “An exception is the signal from a method to its caller that it didn’t fulfill its contract.” So, as a caller, if I don’t get an exception, I can safely assume the method did its job, completely. (Of course, the question still has to be answered case-by-case, which special outcomes lie within the contract, thus not being worth an exception.)
Even though that might seem self-explanatory, there’s lots of mis-understanding among developers on that topic.