Error Handling and Exceptions in Java
Table of Contents
- Intended Audience
- Introduction
- Popular False Premise
- Always Know: Anything May Fail!
- Two Groups of Exceptional Conditions
- What I Mean by “Handling”?
- Ultimate Objectives of Error Handling
- Thoughtful Design and Planning vs. an Afterthought
- Consolidated Error Handling
- Exceptions Fundamentals
- Java Exceptions
- Noble Idea
- Myth: Checked Exceptions Add Safety
- Myth: Recoverable vs. Unrecoverable
- Myth: Unchecked Exceptions are Bad and Dangerous
- Reality: Correctly Handling Checked Exceptions is a Lot of Work
- Business Exceptions
- Avoid Using Exceptions to Communicate Business Data
- Use Unchecked/Runtime Exceptions
- Packaging Exception Classes
- Summary
Intended Audience
There are countless books, articles, blogs, and software forum discussions on the subject of error handling. So, why am I writing another article that stands little chance of even being picked up by Google – in the sea of all other Internet posts and publications? First and foremost, based on the real-world projects I see every day, I strongly believe that error handling (or the lack thereof) in Java applications today is a huge problem that, on a large scale, is not being solved – or even seriously considered. Clumsy, improper, often thoughtless attempts on error-handling cripple thousands of Java projects making them unjustifiably complex and… error-prone! Second, I am all but convinced by now that this sad state of affairs takes place not despite the fact that there are so many books and articles written on the subject but rather due to the fact that the vast majority of those publications are forcefully promoting (unintentionally, of course) bad practices and fundamentally flawed concepts. On the other hand, those who have long figured out the better ways of doing things, keep relatively quiet as if they want to keep the secrets to themselves or, most likely, do not want to stir up the controversy or lose some of their followers. They often simply say: “do it this way”, but they won’t go to a great length to explain why. They know why, they know it works very well, and it is very simple. So they just make a well-meaning suggestion in a forum, and walk away – leaving it up to the more-than-enthusiastic “old-school” zealots to pour dirt all over their comment, dismissing all or most of it – often without even reading the whole thing.
Most of today’s cutting edge Java frameworks and latest specifications seem to be doing things exactly right. The Spring Framework, or even the EJB 3.x specification are just a couple of the most obvious examples. However, I have not yet seen a book or article written by a well-respected Java authority that would openly and forcefully explain the true essense of error handling, why some approaches are better and safer than others, and why projects like Spring, for example, are chosing their way of handling errors while shying away from one of the most hyped-up features of the Java language. Most publications, in fact, continue to promote the same principles, theories, and stereotypes that have proven to fail so miserably. Many egos are at stake, software engineers generally don’t like to admit that they were wrong or that their well-intended solution didn’t work very well after all. And I honestly think that it seriously hurts our industry.
If you have been frustrated or dissatisfied with the way your projects handle errors, if you have witnessed how teams struggle chasing subtle untraceable exceptions that provide no answers, and if you are genuinely looking for a better way of doing things, read on. I hope this article answers many of your questions and helps you get on the right track with your next project.
I’d also like to note that I do not intend to convert anyone who is perfectly happy with the way they are doing things now, if they believe that their approaches, however different from mine, work well for them.
Introduction
Popular False Premise
One fundamental flaw of most solutions to error handling in software is that error handling is approached from the wrong end, to begin with. Here’s a typical statement taken from a randomly selected online tutorial on exception handling: “...when you write a method that may fail, you must indicate that it may fail and how it may fail.” Please note the italicized text. Although the exact wording may vary, this is, generally, the fundamental premise of the vast majority of books, tutorials, blogs, or online articles on error handling you can find today. This is the axiom, the taken for granted fact that makes the basis for all error-handling methodologies and “best” practices promoted in all those publications. Unfortunately, the very premise that some methods may fail and some may not is just plain wrong!
Amazingly, it is a commonly accepted and hardly disputed assumption that if a module may(!) produce an error, then an indication of that possibility must be somehow built into the module to let programmers know whether or not they should provide an error-handling strategy for that module. In other words, the common thinking is: “Let’s see if the function tells us that an error may occur inside; if yes, we will decide how to handle the error.” If you have just read this and did not immediately see the mind-boggling ridiculousness of that assumption, you may be one of the many who have fallen into the trap. It is this philosophy that makes software systems vulnerable by not at all “expecting” large sets of error conditions, and turns error-handling solutions into unmanageable, cumbersome, inefficient, and unreliable behemoths. I will try to do my best to explain.
It takes a 180-degree change of the view angle to see how simple and elegant error handling may – and must – be.
Always Know: Anything May Fail!
As I have just pointed out, the common premise is based on the assumption that all functions in a software program may be divided in two categories: the ones that may fail (according to the programmer who designs the operation), and the ones for which the programmer does not expect any failures. That assumption is incorrect and dangerous in its core. If you understand the simple concept of a function or method in a programming language, you will easily see why.
A subroutine (a.k.a “method”, “function”, etc.) in a programming language is the most basic form of abstraction. The main purpose of abstraction in software is to hide the implementation details of a particular functional unit, and provide simplicity of usage by exposing only a minimalistic interface via which the clients/callers may effectively invoke the abstracted functionality. Among many benefits, the concept allows to later modify the implementation details without affecting any code that depends on it. The very definition of abstraction implies that no entity that uses the abstracted functionality is in the position to make any assumptions about the implementation details of that unit and may only be aware of the exposed interface. Bingo! (Oh, and before someone even thinks about stating that silly, childish argument… No, it absolutely does not matter if the same programmer writes the client code and the function the code call!) Once something is abstracted, you are not supposed to make any assumptions of the abstracted details! Period.
Even if the designer of the particular method thinks that the method is very simple and solid, it is not responsible (and not smart) to state or imply that it may never fail. A failure may happen due to a wide variety of reasons. The method may have bugs in it. The implementation of the method, or any underlying libraries it depends on may change in the future. Some low-level code that the method depends on may have subtle bugs or not designed to work as expected by a client – in certain environments. And so on. Of course, there are two different types of failures: system/software errors, and purposefully designed/signaled exceptional conditions whose intent is to reflect different types of legitimate business situations that disallow the successful execution of the “main” workflow. We will talk about these differences later. Some people would argue that the “possible failure” premise we mentioned in the beginning usually implies the latter type (“business” conditions.) It doesn’t matter. The only safe and responsible way to program is to always KNOW that any method may potentially fail to perform its main task (if not due to a business condition then due to some other type of error) and design for both successful and negative outcomes. Assuming that some methods may fail and some may not will put you on a wrong track from the start.
Two Groups of Exceptional Conditions
Since anything may fail, it is irresponsible and incompetent of a programmer to leave some operations out of the error handling strategy simply because they do not explicitly indicate that an error might occur inside. In other words, it makes no sense at all to specify on an individual basis whether a method or component may result in an error or not. The question, therefore, is not WHETHER but WHEN and HOW different types/classes of error conditions should be handled in the system. A properly designed system must ultimately handle ALL errors, one way or the other. Therefore, the application designer faces a fairly simple task of dividing all potential exceptional conditions that may occur within the system into two very basic catecories:
- the few that actually matter; these are the conditions that must be handled individually due to the fact that the system is expected to (can and must) do something specific in case of such conditions – as defined by the actual needs of the application (requirements), and nothing else; such conditions may be further divided into classes that require (by design!) more specific individual handling; the application designer/programmer may identify and add new error types or exceptional conditions to this group at any stage of development;
- everything else, i.e. all other exceptional conditions that the system cannot and/or does not care to provide any form of recovery other than, perhaps, logging and graceful termination.
What I Mean by “Handling”?
For the sake of clarity, I want to note that the term “error handling” in this article means not only “recovering” from them and implementing an alternative action. When I say “handling” I absolutely mean any type of action – by the program or programmer – to process the error, including managed program termination. This means that programmer’s bugs must be handled too – in a special way, since you can’t guarantee that one of such bugs doesn’t make its way into Production, and nothing should simply “blow up” in Production. Regardless of whether your software runs on a space shuttle where the lives of the astronauts are at stake, or you are developing a shopping cart web application where the last thing the end users should be exposed to is some bug in your code.) The answers to the “when” and “how” questions are provided by the system requirements, and nothing else. Period. Of course, it may be fine to allow a little test application to spits an exception stack trace in your face when it blows up. However, every finished professional application that is intended to run in a production environment should always be designed to direct the most complete error logs to a safe logging destination and exit gracefully even in cases of unrecoverable errors.
The term “handler”, as used in this article, refers to a dedicated software module that implements an action to manage a specific class or group of exceptional conditions.
Ultimate Objectives of Error Handling
When an error occurs, the module must, of course, broadcast that error information somehow. So, the job of proper error handling is really to provide a mechanism that ensures that the error signals (the tokens that carry the information about the error and its origin) are fired and freely propagate from the sources of errors to the strategically placed handlers without affecting the business logic.
Thoughtful Design and Planning vs. an Afterthought
The biggest mistake a developer or architect can make with regards to error handling in a software system is treat it as a secondary issue, something that is less important than the “main” functionality, something that may be added at the last moment.
In a proper design and analysis of any software module or system equal consideration must be given to the desired behavior of the system for both – successful execution and any types of failure. Depending on the system, the latter may be a simple single use case, or a set of quite elaborate use cases whose complexity may measure up to or even outweigh the complexity of the successful work flow.
Therefore, error handling strategies must be considered an essential part of the system requirements and design. Error handling may never be an afterthought, something that programmers may (or may forget to) add after the “main” work flow is implemented. Regardless of error types (e.g. critical unrecoverable system failures or predictable business conditions treated as errors), the system must be designed to accommodate for graceful and efficient handling of each such class of conditions.
As I have stated in the Introduction to this article, when approaching error handling in software development, perhaps, the most important thing to keep in mind is the fact that everything can potentially result in an error, and a failure must be always considered as a possible outcome of an operation. This means that any given method call may potentially produce an error of some sort. There are no operations whose successful execution may be unconditionally guaranteed at any time.
The purpose of error-handling design is not to determine what may fail (since anything may) but to determine how to appropriately divide the complete error set into the sub-sets relevant to the application’s desired behavior, and where in the system each such class of failures must be treated. Fortunately, even although errors may occur in thousands of places, a typical application only needs to distinguish between a few types/classes of such conditions. This means that only a few error handling strategies normally need to be implemented per application, and all errors must simply be sorted out and properly directed to the appropriate handler.
Consolidated Error Handling
Centralized and planned error handling is key to building efficient error-proof software systems. Regardless of the philosophy or technology used, the fact remains that error handling may never be a thoughtless knee-jerk reaction to some alerts automatically generated by a compiler or any other tool. The decision when and how to handle errors – specific or any – must always ultimately be made by the programmer based on a thoughtful consideration of the application requirements and each particular use case.
Errors may not be reliably handled or controlled in a sporadic decentralized manner. Instead of being sprinkled all over the application, error handling must be consolidated to ensure clarity, reliability, coherence, and prevent a loss of any valuable information about the cause of the error.
In the agile development environment, each new requirement and exceptional case must be analyzed to determine which part of the system should be responsible for handling it. In a case of an exceptional error condition, the call stack must immediately unwind all the way to the dedicated handler that possesses the contextual knowledge and ability to handle the error properly. The error data must reach such handlers in its original form, unaltered, with its complete stack trace. The original error information may be supplemented with additional helpful data or message – but never shortened, replaced, or otherwise altered. This is the purpose of exceptions.
Exceptions Fundamentals
Exceptions in a programming language essentially serve the following purpose. They trigger an immediate unwinding of the call stack (abort the thread execution) and signal the fact and nature of the error – to anyone along the call stack who is willing to listen and take action. This, obviously, provides many significant advantages over the old-fashioned error codes. Methods may throw more than one type of exception, and each such condition may be caught/handled separately and in different methods – without cumbersome conditional logic throughout the call stack. Most importantly, exceptions allow implementing error-handling code only in those modules where it is actually appropriate – instead of doing it in every module in the call stack starting immediately at the origin of the exceptional condition. The latter is inevitable with error codes that must be checked for by each link in the call chain and propagated to the one that actually knows what to do with the error. On the other hand, with exceptions, any methods that can’t really contribute to handling the error can remain completely free of any error-handling logic whatsoever while focusing on their business logic instead. All this can lead to much cleaner code and consolidated error-handling functionality.
When the exception mechanism triggers an exception, the instance of that exception class is broadcast to all the methods on the chain of method calls that eventually resulted in the exception. This means that any of those methods can potentially be coded to catch and handle the exception, if necessary, while others can ignore it. Naturally, there is always one such method in the call chain that is better suited to handle the error than the rest. Which one? The correct answer can only be provided by the application designer, the programmer that is. No tool – a compiler or IDE – can accurately, with 100% certainty determine where in the call stack the given error must be caught and handled. The decision usually depends on the circumstances, such as the application’s or use case requirements, etc. So, it is the programmer who has the responsibility to carefully consider all those factors. The error handling constructs must be implemented in the module that contains the sufficient contextual knowledge and ability to take the appropriate corrective action in response to the condition.
One thing that can be stated with certainty is that in the vast majority of cases, the most appropriate place to handle an error is NOT immediately at the origin of the error. The immediate caller of the method that triggers an exception rarely can and should do anything about the error – simply because it does not have enough contextual knowledge of the circumstances in which it itself was called. So, in the majority of cases, the role of the exception should be to efficiently and transparently carry the error signal through the call stack to the one method that has the intelligence to handle that exceptional condition.
Java Exceptions
Noble Idea
In addition to the “traditional” approach to exception handling described in the previous section, Java has introduced a new type of exceptions – checked exceptions. Such exceptions, once thrown by a method, must be defined in the method signature (for unchecked exceptions that is optional), and the compiler forces any immediate caller of the method to take action. To satisfy the compiler, the immediate caller must either catch and handle the exception itself, or to add it to its own signature, which, in turn, subjects each of its own callers to the same restrictions.
The noble idea behind checked exceptions was to introduce a harness mechanism that would remind/force developers to handle errors before they bubble up to the surface and break the application. This concept may be used with benefits in small tight sub-systems where it is, indeed, necessary to force the immediate caller to handle the error at its origin. However, as noted above, such cases, while valid and existent, make up only a small minority of all real-life situations. So, in a vast majority of cases, for proper handling, an exception absolutely must be propagated further up the call stack – to the only appropriate module that was designated to handle the exception. And this is where checked exceptions introduce more problems than they solve.
Read the interview with Anders Hejlsberg (the Chief Architect of the C# project) where the subject of checked exceptions is discussed in great detail. In that interview given back in 2003, Hejlsberg explains the most obvious pitfalls of the Java’s implementation of checked exceptions, and why the C# committee had unanimously rejected the idea. He specifically talks about such issues as broken encapsulation, inappropriately forced contracts, negative effect on scalability and “versionabiliy” of systems built with checked exceptions.
Myth: Checked Exceptions Add Safety
In addition to the issues highlighted by Hejlsberg, perhaps, the most essential – and often completely overlooked – is the fact that, in practice, checked exceptions have failed their most hyped promise: increased safety. Actually, the myth of added safety is arguably the most outrageous and dangerous misconception promoted by the advocates of checked exceptions. In fact, if I doubted the sincerity and good intentions of the advocates of checked exceptions, I would call it an outright lie.
Whether the authors of checked exceptions meant this or not, relying on the compiler to indicate which methods can throw exceptions (e.g. fail to do what’s expected from it) led so many programmers to disregard the fact that anything may fail under certain circumstances and worry only about a small subset of all potential errors represented by checked exceptions – leaving huge holes in their applications. (I am not theorizing here: it is merely a fact of life, a pathetic reality that can be observed on thousands of Java projects.)
Many people – perhaps, even subconsciously – interpret the mere presence of checked exceptions in the language as a suggestion that some modules/methods may result in errors while others may not! So, they forget to handle anything other than what the compiler forces them to handle… Need I say more?
Myth: Recoverable vs. Unrecoverable
Another very common and dangerously misleading guideline – widely promoted by various books, articles, and, most importantly, by Sun Microsystems in their tutorial – is that checked exceptions are meant to indicate “recoverable” conditions, while unchecked exceptions represent only errors (programming errors, i.e. bugs.) Some less experienced (or less competent) programmers even conclude from the latter that since runtime exceptions are “unrecoverable”, applications should not even do anything about them! (I only hope those guys are not employed by companies that build software for systems where human lives are at stake!) The “Recoverable vs. Unrecoverable” thesis is also suggested in one of my most favorite and respected books on Java – Joshua Bloch’s “Effective Java”, which is quite understood considering that the book was written by one of the authors of the JDK. With all fairness, Bloch also stresses that checked exceptions should be used with caution and not overused. He suggests to exercise the best judgement when deciding what should be considered recoverable, and when in doubt, opt for an unchecked exception. And that is the approach I use. However, the only conclusion I usually arrive at is that I am in no position to decide for the client how they would want and need to act upon the specific exception. An API defines how a client accesses the unit of functionality, and what results it should expect. No API can dictate how the client should use the result after the result has been obtained. Therefore, only the caller can and should decide what it wants to recover from, and what it does not care to recover from – based on its own needs and not on the implementation detail of the module it calls. On the other hand, any exceptional condition must be handled by a correctly designed system – at some point. The question is where and when. So, it is also very, very wrong to suggest that runtime exceptions are meant to be ignored.
Myth: Unchecked Exceptions are Bad and Dangerous
Inevitably, it has become typical to handle checked exceptions and completely disregard unchecked exceptions, only to act surprised when the application suddenly crashes. This, in turn, has generated a phobia of unchecked/run-time exceptions among inexperienced programmers. Those who mistakenly believe that run-time exceptions should not be handled, blame them for application crashes. As the result, a large number of Java programmers today does not even realize that it is their responsibility to design for error handling. A runtime exception is a very useful carrier of error information that is openly available for anyone in the call chain who is interested. Unfortunately, many developers show no interest at all in listening for what’s out there for them to use. Instead, they all but thoughtlessly react to a compiler alert (often feeling annoyed) and do something just to make the compiler error go away. Needless to say, such approach cannot lead to reliable error handling.
It is absolutely incorrect to assume that all runtime exceptions should not be caught and allowed to propagate to the very “top” of the application. As I have stressed at the beginning of this article, for every exceptional condition that is required to be handled distinctly – by the system/business requirements – programmers must decide where to catch it and what to do once the condition is caught. This must be done strictly according to the actual needs of the application, not based on a compiler alert. All other errors must be allowed to freely propagate to the topmost handler where they would be logged and a graceful (perhaps, termination) action will be taken.
Reality: Correctly Handling Checked Exceptions is a Lot of Work
In large systems, a significant amount of cumbersome, repetitive boilerplate code is necessary to handle checked exceptions properly – to ensure that the error information indeed propagates to the dedicated handler instead of being mishandled. The try/catch constructs and throw clauses litter multiple methods and layers throughout the application. They unjustifiably pollute and bloat methods that have neither knowledge or ability to do anything about the errors they catch and re-throw.
In reality, checked exceptions end up confusing programmers, prompting them to handle or swallow exceptions before they reach the proper handler, and ultimately introducing more problems than they solve.
Business Exceptions
Avoid Using Exceptions to Communicate Business Data
Use Unchecked/Runtime Exceptions
NOTE: You should carefully read the previous chapters in this article before reading this!
Developers of the modules that have no business of handling a particular error as it passes through do not need to be aware of that error at all. They do not need to bother with implementing any code to handle that error. Instead, only one module in the whole application must be implemented to listen to that particular exception, catch it when it bubbles up, and handle it according to the application requirements. Combined with the mandatory common-sense understanding that anything might potentially fail, runtime exceptions become a super-efficient tool that allows to consolidate error handling in a few strategically placed modules, and with bullet-proof safety.
Like any technology or tool, unchecked exceptions may be mishandled, true. However, it is very easy to spot – during testing – a case when a particular run-time exception bubbles up way too close to the surface (or crashes the application!) instead of being handled earlier. A detection of such case indicates that a new handler must be introduced at a deeper level (as needed by the system) that will intercept that particular error before the more generic handler. No modifications to any business code would be required. It is significantly easier and less time-consuming than chasing a usually obscure problem caused by a mishandled checked exception.
While there are cases where a checked exception does no harm and serves good purpose, for the sake of consistency and to avoid error-handling chaos, I recommend using only unchecked exceptions. Unfortunately, the particular implementation of the checked exceptions concept in Java introduces so many possibilities and encouragements for doing things wrong that any benefits of exception-re-enforced contracts between methods (where they may help) pale compared to the overall damage this mechanism causes to the software projects all over the world, and, most importantly, to the very perception of the error handling task and the role of a programmer in it.
Checked JDK and 3rd-party exceptions could always be caught inside the implementations of the your classes, wrapped into module-specific run-time exceptions (usually, with an additional clarifying message), and re-thrown only to be caught and appropriately handled by the designated handler upstream.
Packaging Exception Classes
Each exception class should normally reside inside the package with the classes that implement the exact functionality the exception relates to. It is a common mistake and bad practice to use a dedicated exceptions package that combines all exception classes for an application or component. Such approach violates modularity by forcing unnecessary package couplings: all other packages in the application become conjoint at the common exceptions package.
Summary
I think that one very important but simple point is missing from most discussions on this subject: anything may fail. Period. I just can’t stress this enough. If we insist that something in the method signature must remind the programmer about it, then every method’s signature must be equipped with such indication, e.g. “throws Exception”. The fact that some methods in Java are declared throwing an exception (checked) and some are not is seriously misleading since it implies that some methods may never result in errors, or if they do, the programmers may not need to worry about it. And so many of them don’t.
When an error occurs within a function – regardless of whether the function declares a checked exception or not – that should never be a surprise to a developer! Just know that it may happen, and stop looking at the compiler to tell you whether it may or may not. It may. Period. End of story.
Programmers must never rely on the source of the potential error to tell them whether they should expect an error or not!!! The necessity for error handling must be unconditionally assumed by all programmers at any time. What this means is that the programmer must always KNOW and REMEMBER that any module or operation may potentially – in certain conditions – fail or misfire. Once this simple fact is understood, and the application programmer knows that the module her code depends on may fail, all she has to decide is WHERE in the application (not WHETHER!) she should handle that potential class of errors – without having to do something about the error immediately at its origin. This is what we should be teaching programmers! Instead, we are telling them: “Hey, we have this cool feature in Java that will tell you precisely when you need to worry about errors; in case a method signature declares an exception, you should pay attention…” Are we kidding ourselves? Does anyone see how horribly destructive – and foolish – such approach is? It is one of the most harmful but well-concealed philosophies that has ever been burnt into the minds of millions of programmers. I can’t tell you how many times I have heard a developer scream with rage at his application exploding in his face: “What the …!!! I have taken care of all the exceptions and it still blows up!” What they are saying is that they had caught all the checked exceptions in their code, but didn’t even think about any other potential failures. Checked exceptions re-enforce this horrific mindset. Instead of consciously expecting negative results as a given possible outcome of any operation and thoughtfully designing against them, we wait for the compiler to tell us whether we should worry about a possible error or not! That is just ridiculous. The implication of checked exceptions (not intentional, of course) is that if the method does not throw a checked exception, there’s nothing to worry about. We know that it is not true because checked exceptions are just a subset of all possible exceptions that may be thrown by the code that implements the operation. And if we know that, we understand that even if we handle the checked exceptions, at some point we must take care of everything else. And if so, we don’t need checked exceptions. The problem is that many today’s Java programmers raised on checked exceptions are not used to think that way. As the result, they often forget to handle the rest of the exceptions. And you can’t really blame them, because – with checked exceptions – the error handling code is everywhere, literally in every single method, i.e. in thousands of places. It is virtually impossible to keep track of what and where is being handled. That is the reason why so many errors are not handled at all, countless errors are mishandled, and so much of valuable error information is lost completely before it reaches the proper place in the system where sense could be made of it, where it could and should be properly handled.
Fortunately, it’s easy to build bullet-proof systems with only unchecked exceptions (much easier than ensuring proper handling of checked ones.) Any application, in reality, only needs to distinguish between a very limited set of exception classes that need to be handled separately by strategically placed dedicated handlers to which these classes of exceptions should propagate without being intercepted. Everything else should be allowed to bubble up to the “ultimate” top-level (the “toppest”) handler that catches everything that was not handled before and handles it gracefully. (This means, once such [unchecked] exception is thrown, you don’t have to worry about it anywhere in your code, knowing that it will be caught and handled by your top-level handler.) So, you normally start with creating such top-level handler that will catch everything inside the system.
For example, in a Spring MVC application that would be an exception resolver class configured to catch and handle the most generic Exception class, and, perhaps, resolving the error to the generic error page that politely apologizes to the user and informs the user that the system is currently not able to process the request, etc. Of course, in a case of such web application, there must also be a filter defined in the web.xml file that would ensure that any critical exceptions that may potentially occur in JSP tags on the pages would also be caught and the application redirected to an error page. Such filter is required because any JSP tag exceptions occur outside the Spring-managed controller and will not be dispatched to the resolver registered in the Spring application context.
Next, we need to identify the classes of conditions that must to be handled individually in a distinct manner. We decide where such events should be handled in our system – based on our system’s requirements, and nothing else! For example, in Spring MVC web applications, often a single generic exception resolver class may be all you need to handle all your server-side exceptions! Such exception resolver would resolve all distinct exception classes by implementing the mappings between the exception classes and views (or, in the case of Spring WebFlow, flow states.) Based on the class of the exception thrown anywhere within the application, it will redirect the request to the appropriate view or state – possibly, after executing some logic, if necessary. The resolver would always fall back on the generic “catch-all” redirection, in case the caught exception class is not explicitly mapped to any view or state.
If, during testing, we discover that some exception propagates too far (e.g. to the topmost handler) instead of being handled earlier, well, we immediately know that another lower-level handler is required (or that the particular exception should be caught by one of the existing lower-level handlers.) That’s it. Very simple. And very reliable.
There's some good stuff here, but your emphasized statement that "the compiler forces any immediate caller of the method to take action." is simply wrong. Java always gives you the option to declare that the caller itself throws the exception, and this does not take action on the exception. It merely passes the buck up the chain, and as you indicate a lot of the time this exactly the right thing to do. The advantage of a checked exception here is that nobody gets surprised when the exception is thrown. It's part of the method signature.
Requiring only runtime exceptions is as bad as requiring only checked exceptions. It's not that hard to figure out when each is appropriate. briefly, if the exception indicates a bug in your code, it's a runtime exception. If it doesn't, it should be a checked exception.
Checked exceptions assist in propagating error conditions to the right place to handle them. Yes, programmers misuse them, and often use try-catch where they should be using throws; but eliminating checked exceptions completely is like forbidding floating point arithmetic and insisting we use only integers because many programmers don't account for round-off.
Elliott,
thanks for your comment. I am afraid you may have missed the point I was trying to make. First, when I say that the "compiler forces the caller (i.e. the programmer who writes the caller method) to take immediate action" I mean a) handling; or b) wrapping and re-throwing; or c) adding that very exception to the method signature and forcing the next caller in the chain to consider the same options. And so on. Option c), indeed, is one of the things that the programmer is forced to consider. There is NO option to ignore.
"Requiring only runtime exceptions is as bad as requiring only checked exceptions."
Again, I beg to differ. It has been said many times by many authors that combining the two types in the same application creates nothing but confusion. But, obviously, you can't have only checked exceptions. It is physically impossible. Considering the fact that I stressed at the very beginning of the article (that anything may fail), every single method would have to declare the generic "throws Exception" in its signature, and nothing would make sense. Unchecked exceptions, on the other hand, may be used exclusively with ease, and great efficiency, with no burden on the business logic.
I have a long list of very successful projects and applications that I have personally built in the past years for quite serious clients (large companies, start-ups, government entities, e.g. the United States Postal Service), and none of these applications use checked exceptions. I can proudly say that there were projects in my career that I delivered to QA with virtually no defects, and the applications that I build, as a rule, are deployed into production without a single issue. Many are running smoothly as we speak. On very rare occasions, yes, an issue might come up, but it is always instantly traced – thanks to a very clear, transparent, and informative error handling. I prefer to spare myself from polluting the business logic with try/catch constructs, and all my error handling is always consolidated in a few well-defined places. (Something that is NOT possible with checked exceptions.) My views in this article are not pulled out of the thin air. I stand behind everything I have written because I have years of experience and many applications to shopw for it. I write code for a living, and I practice what I preach. I am not afraid to say this in a public forum – well aware of the fact that this may be read by people who have actually seen my code. My exception handling just works. Without checked exceptions. I would gladly challenge anyone to look at my code and state otherwise.
"Checked exceptions assist in propagating error conditions to the right place to handle them. "
Again, Elliotte, I would completely disagree with you here. Based on thousands of real-life examples that I have seen, they assist in the opposite. They all but encourage stopping that propagation way to early. And that's what happens more often than not.
Finally, my main point in the article was that "programmers may never expect the source of the potential error to tell them whether they should expect an error or not. Anything may fail." therefore you DON'T NEED a checked exception to remind you of that, you MUST ALWAYS KNOW IT, and only worry about WHERE/WHEN (not WHETHER!) to take care of errors.
"eliminating checked exceptions completely is like forbidding floating point arithmetic and insisting we use only integers because many programmers don't account for round-off."
Well, I'm shaking my head… No other programming language has checked exceptions. And that's almost 15 years after Java introduced that "ingenious" invention. (There is a reference to the Anders Hejlsberg interview in my post where Hejlsberg talks about why the C# committee unanimously dismissed that idea.) No one followed suit. Not by accident, but for a reason. Enough said. Even Sun is abandoning checked exceptions. Remember, there are no checked exceptions in the EJB 3 spec…
Constantine,
wow, this is a very thoughtful article on the subject! One of the best I have seen in years, in my opinion. Definitely was worth my time reading. I have gradually switched to using mostly runtime exceptions after many years of using checked exceptions. And I tend to agree that the proper use of RTEs makes error handling much simpler and safer. I think many people are simply afraid to go against the well-established (outdated?) principles. Thank you for this formidable effort. I will be recommending this article to my colleagues.
Dan Furchess
Just a few typos in this most beautiful piece of programming knowledge:
1- to spits
2- of the your classes
3- to shopw for it
4- Elliotte
Thanks, liked it. As a junior jee developer i was looking for some answers on exception handling and never found a good article before this one 🙂
I've always asked myself what to do with checked exceptions that i shouldn't catch on the direct caller, didn't think some people wrap it into runtime exception…
BTW that's a pretty big text and there are not enough exemples i think.
Constantine,
I like your article and your backing information/argument. I'm curious, if this approach has served you well and/or if you've found in the three years since you rote this, that you'd like to update this article or change anything?
Thanks,
Ken
@KSev: Ken, thanks for your comment. Yes, absolutely, I still stand by each word today. More than ever! I have been using this approach for many years now, on new projects, or refactoring legacy code. Amazingly, error handling in Java projects I see is still largely mishandled – for the same reasons I describe in the article. Checked exceptions – and their mindless usage – still prevail, and many developers are struggling to understand what it is that they need to do to write safe and clean code that gracefully handles and reports errors. A proper use of RTEs implies that in most cases, the developer should only be concerned with throwing the appropriate precise, descriptive RTEs anywhere in their code where it is necessary to *broadcast* an exceptional condition. In very few places there should be the code that actually catches exceptions. Only a few centralized and thoughtfully designed (architecturally positioned) handlers/listeners together should take care of *all* those RTEs, regardless of where in the code they are generated, including logging the full stack trace. A separate external "mapper" may be used (e.g. injected into those handlers) to translate certain exception instances/types into user-friendly information, whatever it may be. Instead programmers continue to all but thoughtlessly catch exceptions every step of the way, often misusing the exception message (the string returned by getMessage()) for communicating info to non-programmer users, etc. The nightmare continues…
You claim unchecked exceptions do harm to the world based on examples of things that have gone wrong due to people misunderstanding and abusing them. However, *objective harm* would only be caused if all that broken code out there would have been better if Java had been invented without checked exceptions (and still been such a success).
That's the fundamental flaw of your whole point of view. Without checked exceptions, the Java world would not be a better place, it would be worse.
Assume in 100 places a checked exception was used, in 80 places it caused bad code. Does this mean checked exceptions are harmful?
No, it does not follow. Instead, you have to compare to what the situation *would have been* without checked exceptions. And then, you would see that if in the same 100 places, if no checked exceptions would have been used, the same stupid developers would have created bad code in 90 of the places.
And that is why checked exceptions are good, they create safer code even in practice, statistically, because dumb people screw up code even worse without checked exceptions than with checked exceptions.
That being said, it is still possible to say that your recommendation is good for certain types of business. E.g. when you have a team of largely inadequate programmers, telling them to only use unchecked exceptions and having one smart guy handling those in key places may work out better than telling everybody to use checked exception as they see fit. But even then it is good that java was shipped with checked exceptions, because it allows teams to decide whether and how to use them, depending on the team skills.
Sun dropping checked exceptions is not a sign of them being useless or harmful, but merely unpopular.
Also, "Anything may fail" is not a useful assumption for writing specific error handlers. Of course there needs to be a central place in any system that catches all unchecked exceptions and handles those, but that place can never handle all possible exceptions adequately. Instead, specific exceptions should often be handled in other places closer to the error doing smart recovery, and you offer no adequate strategy at as how to identify such exceptions early on. Testing and more testing and even more testing is not working for many teams under time and budget restrictions. Checked exceptions are good for that, they allow to spot an opportunity to do specific error handling without first having to write a test covering the corner case that fails for this exception.
As to other languages of the last 15 years, since those are not designed by large organisations but by small labs or individual, they need to be appealing to programmers to have a chance to be used. So the language designer there need to implement language features that look hip to the hipsters, not language features that are important for building large sustainable systems. That alone explains the absence of checked exceptions.
Good article! After all these years, it's surprising to see the disorientation that still persists in much of the Java community around this issue. In fact I was considering writing myself an article until I found yours, which clearly expresses the same conclusions I reached some years ago.
(Side note: Instead of using only unchecked exceptions, in my team we follow the workaround of simply declaring "throws Exception" for almost all methods, except getters and other trivial methods. Not better than your approach, but also effective.)
I think that checked exceptions work well only in low-level libraries. This is the reason they seem a good idea when you see them applied in the JDK itself. For the rest of application layers the idea simply doesn't work, for the reasons you have explained. It's just a Java design flaw.
One point that is worth stressing, I think, is the problem of root cause hiding. Propagating checked exception declarations is not feasible without breaking encapsulation, so the workaround is usually polluting the code with lots of useless try-catch blocks that hide the root error cause by wrapping it in layer-specific exceptions. This is not a fault of the programmer, it is a consequence of following the "official" checked exception guidelines. Hiding the real error cause is a big problem: logs that nobody understands, and inability to directly catch the real cause in a top-level handler.
I also completely agree with the need of "thoughtful design" for error handling from the beginning, and constantly strive to mentor my team on this need.
Joan, thanks for your comment. I see your reasoning behind the workaround you have mentioned. Yes, with all methods defined to throw Exception you no longer have to put unnecessary try/catches inside the methods, but it seems unnecessary to me. IMHO, it would be cleaner and simpler to use meaningful context-specific custom RT exception classes to wrap any JDK or 3rd party checked exceptions and re-throw, while never throwing checked exceptions in your proprietary code. But, I think we agree in general: the design should always consider the fact that anything may fail and that for all classes of errors there should be corresponding handlers. An exception (checked or RT) should also be caught each time your code can/should add more context-specific information that is available at the moment and could help to better describe the issue. In such cases, the original caught exception should be wrapped inside a meaningfully named, context-specific custom RTE class instance and re-thrown.
It is a shame that most (if not all) of the critics only glance over the text, and then write something like "you are wrong", with everything that follows indicating that they didn't even read or understand the arguments. No matter how much I try to explain the idea and cover all the bases, the arguments often fall on dead ears. Sometimes, I get nothing more than some uneducated gibberish from an offended believer who can't tolerate that someone has taken on the dogma they were raised with
Regarding exception wrapping, time ago I came up with a very valuable guideline: do not wrap or convert an exception to another, unless the new exception provides more information about the *real cause* of the problem.
In my team we have followed this simple guideline for years, with success: meaningful error logs, robust error handling.
Example: when a web service is parsing a JSON request, it encapsulates a JSONException into an InvalidRequestException. Because the latter is more informative about the real cause of the problem (an invalid request was received). You can still keep the original JSONException in the chain (as the "cause" exception), to preserve the details about the problem (a malformed JSON structure).
Another example: BookRepository should NOT wrap an SQLException into a BookRepositoryException, because the latter does not provide more information about why the database has returned an error. It rather provides information about the context/place/layer where the exception has been detected, but this information is not related to the real cause of the problem.
If you throw a BookRepositoryException, you are hiding the real cause. What if an upper layer (e.g. the application layer) knows how to handle SQLExceptions? Let's say, by clearing a DB connection pool, or simply by logging them.
Unfortunately, Java checked exceptions will usually force you to define a BookRepositoryException. Because at the interface level you do not want to assume a database-based implementation of the repository, and hence you do not want to add "throws SQLException" to the interface.
The solution is however simple: forget about checked exceptions, simply declare your interface as "throws Exception" and let the SQLException propagate transparently through your interface.
You can also follow your approach: only throw unchecked exceptions. But unfortunately SQLException is already defined by the JDK as a checked exception, so you will need to wrap it. To avoid hiding the real cause, you will need to define (and catch) something like a RuntimeSQLException (by the way, this is precisely what Spring does, I think), and the same for the rest of checked exceptions thrown by the JDK and other third-party libraries. A lot of work to be done. This is why I generally prefer the simpler "throws Exception" approach. Anyway, as you have said, we agree on the general lines.
Ok, I see your rationale, and it certainly works well if you indeed want to ensure uninterrupted propagation of the original checked exception all the way to your handler. I personally might argue that there is no harm (and I actually see added value) in wrapping a very generic (and often not very informative SQLException) in an application-specific (business-service-specific) exception that may clarify which data access operation is actually failing and what the specific context is. As you have said, the original SQLException will still be in the stack trace available for analysis. I like the way Spring does SQLException translation, and often the Spring exceptions are all I need. But in some cases, exactly as you point out, and as I have stressed in my article, if I can add even more specific helpful info, I'd wrap it in my own even more specific RTE. Nothing prevents you (or whomever is writing the clients for your modules) to tailor the handlers to unwrap the nested causes – if needed. Those are just the details, and one approach may be chosen vs the other depending on a project/application/use case, etc. Good discussion.
@Anonymous: thanks for taking the time to post your thoughts. Unfortunately, you seem to have missed or ignored much of what I tried to explain. You are correct, nothing can prevent a bad programmer from writing bad code. However, that is not the point. All other widely used programming languages work just fine – and better – without checked exceptions. An exception is, in fact, a programmer's friend – it is a very powerful and useful vehicle for delivering information to whomever is listening for it. It is a programmer's responsibility to create such a listener (yes, no one has yet suggested that a programmer could be a mindless fool, although many of those exist, unfortunately.) So, one of the most essential rules that go along with exceptions is that they should NOT BE INTERRUPTED (other than for supplementing the error data with more useful contextual information) – in order to reach the handler and preserve the actual information about the error. The fundamental flaw of checked exceptions is that they are DESIGNED TO BE INTERRUPTED every step of the way. Such interruptions tremendously increase the chances of mishandling errors altogether – with the loss of data. The fact that a bad programmer does "something" (as a knee jerk reaction to a checked exception) not only does not guarantee some kind of partial error handling (e.g."better something than nothing") – but rather ENSURES WRONG HANDLING and information loss. That, in turn, results in untraceable or very subtle and hard-to-track bugs.
In other words, checked exceptions do not ensure "at least partial added value", as you suggest. Instead of helping identifying the problems instantly (which is the purpose of exceptions), checked exceptions increase the chances of hopelessly hiding the root of the problem, making bugs a huge issue, and sending teams on endless goose chases. That's why many teams spend more time on "fixing bugs" and chasing subtle problems in the code than on actually crafting new functionality and building the product. THAT is the problem, indeed.
Quote: "Sun dropping checked exceptions is not a sign of them being useless or harmful, but merely unpopular."
Really? I wish! Unfortunately, checked exceptions are still more popular than not, and most books thoughtlessly continue to repeat the old dogma. New programmers are taught to "like" them. The vast majority of humans are prone to believing in dogmas and following those who speak the loudest. That is the only reason checked exceptions are still part of Java, and people continue to advocate them – often without trying to think for themselves.
I have seen countless Java projects of various sizes and importance – since 2000. I've only seen a few where error handling wasn't a total mess. And those few were overseen either by me personally or by fellow engineers and engineering managers who happen to share similar views on the subject. None of my clients – in the past 10+ years have ever expressed any(!) type of concern related to cryptic, indecipherable, hard to trace error reporting in the applications that I have designed and had control over the implementation from start to finish. I think that says something in favor of my point.
Furthermore, I feel I do need to address a few of your statements that I find especially incorrect and unsubstantiated – for the sake of clarifying the subject for anyone who reads this thread in the future…
Quote:
"You claim unchecked exceptions do harm to the world based on examples of things that have gone wrong due to people misunderstanding and abusing them."
NO, I don't claim that. I claim that CHECKED exceptions do harm because they undermine the very nature of exceptions as a vehicle to communicate error information, because they imply that error handling should not be designed for but rather be a reaction to some code that tells the programmer: "now you handle an error, now you don't." Because they ensure that most developers who use them actually mishandle errors 90-100% of the time by hiding/losing the error information and making bugs hard to find, or, at the very least, by making code unmanageable. They are also harmful because they completely misguide those who are not familiar with other programming languages and learning about exceptions Java. Such programmers risk missing the whole concept.
Quote: ""Anything may fail" is not a useful assumption for writing specific error handlers. Of course there needs to be a central place in any system that catches all unchecked exceptions and handles those, but that place can never handle all possible exceptions adequately. Instead, specific exceptions should often be handled in other places closer to the error doing smart recovery, and you offer no adequate strategy at as how to identify such exceptions early on."
I dis NOT state that exceptions only should be handled at the top level of the application. "Anything may fail" is the ONLY useful and necessary assumption – on EVERY level of your application. Not only at the top level, but at the level of each single method you write. It is fundamentally wrong to rely on someone else's code to tell you whether you should or should not expect an error. But if you, as a programmer, KNOW and REMEMBER that any API or method you are calling may potentially result in error, things become very simple! You know you have to only answer the question: do I have to take care of ANY errors in the underlying code in this given method I am writing, or should I let the higher-level handler do that since I have no context to handle it properly here? THAT IS THE EXACT STRATEGY one should apply for each single function to determine where the lower-level handlers should be. Something you seem to have missed in my article.
Quote:
"As to other languages of the last 15 years, since those are not designed by large organisations but by small labs or individual, they need to be appealing to programmers to have a chance to be used. So the language designer there need to implement language features that look hip to the hipsters, not language features that are important for building large sustainable systems. That alone explains the absence of checked exceptions."
This can't be serious. The world did not begin with Java. I did quite a bit of work in C, C++, C#, Pascal, and even – oh no! – Fortran, PL/1, and Basic… Years before Java came out. Exceptions in programming languages were introduced way before Java. Even Java originally had only unchecked exceptions – for about a year since its introduction in 1995. Then, after much arguing, checked exceptions were pushed into the language by a couple of opinionated bullies on the Java committee who later went on to pat themselves on the back for making Java "safe", which couldn't have been further from the truth. They made Java unsafe by corrupting the very definition of exception handling. That's the only reason why no one has followed the suit…
Thank you for this article and for continuing to respond to comments. I'm a .NET dev who often explores outside of the Microsoft tool chain. Now and then I see some pretty quirky things (from this side of the fence) in Java and can only wonder why. Excellent article.
I usually don’t write Technology comments at all, I don’t have time for it, but today I decided to do so because I really want to tell you to thank you.