Exceptions in Java
Working with exceptions in Java has become among developers a common task. Most of the time, however, the concept of exceptions is misunderstood.
An exception-condition prevents the continuation of a method or the scope that your are in. In some instances you will have sufficient information in the current context to fix the problem. In most of the instances, however, you will not have enough information and you have to hand the problem out to a higher context where someone is qualified to maker the proper decision.
In my past project experience I have seen several Java programmers struggle with the use of exceptions. This varies from simply overusing exceptions and thus making code unreadable or even worse, if used incorrectly, I have come across the wrong use of exceptions which caused immense performance degradation (as it takes memory and processing power to create, throw, and catch exceptions).
You can generally categorise exceptions into three areas:
Programming errors: The application can not usually do anything about these type or exceptions. An example for this category are null-pointers, illegal arguments.
Incorrect usage: Any type of incorrect usage by the API and thus violation of its contract. An example would be the parsing of a not well-formed XML document. In this category, the application is usually within a context, where it can recover from the problem.
Resource failures: Any type of resource-shortage. The applications response to these type of errors are normally context-driven. An example would be the failure of a network-connection or the shortage of memory. In most cases the application is able to retry the operation and recover. Severe resource-failures (such as out-of-memory or corrupt database) will bring the application to a halt.
The Java language defined Unchecked Exceptions and Checked Exceptions.
An Unchecked Exception inherits from RuntimeException (which extends from Exception). The RuntimeException gets special treatment from the JVM and there is no requirement for the application-code to deal with them (hence the name Unchecked Exceptions).
A Checked Exception inherits from the Exception-class. The client code has to handle the checked exceptions either in a try-catch clause or to hand it out to a higher context. A Checked Exception thrown by a lower layer enforces a contract on the invoking layer to catch or throw it. Developers often take shortcuts by suppressing the exception in an empty try-catch block or just throwing it, and thus forwarding the problem to the application’s invoker.
There has been lots of debate in the Java Community (especially with the introduction of .Net) about the Java exception handling. Java is the first OO language dealing with checked and unchecked exceptions, whereas in languages such as C++ or C# all exceptions are unchecked.
A Java developer will at some stage be faced to make a choice between an unchecked exception and a checked Exception and the design-choice really depends upon, if the invoking application can recover from the exception to make it a checked exception, otherwise make it an unchecked exception.
One of the most frequent problems I have come across was, that developers decided to escalate implementation-specific checked exception to the higher layers. One example I came across was the implementation of a TCP/IP library throwing a UnknownHostException to the business logic layer. A better option would have been to either convert the UnknownHostException into another checked exception if the invoking application is expected to recuparate or into an unchecked exception if the invoking application cannot do anything about it.
Another problem I came across was the creation of custom exceptions which meant nothing more than extending Exception. If a developer takes the time to create a new exception just for the purpose of having an indicative name, it would be better to either just revert back to a “normal” Exception or to add member-methods to provide more information to the invoking application.
Many times developers overlook the clean-up of resources. This especially applies, if an API only uses unchecked exceptions. The clean-up can be easily achieved with a try-finally block. In a recent project I reviewed a really good implementation of a networking API containing a connection pool. The developer however overlooked, that in case of a unchecked exception the connection was not returned to the pool. The simple implementation of a finally-block resolved the “unforseen” resource-leakage.
Sometimes developers decide to use exception for flow-control (i.e. to throw an exception once a certain condition is met). The generation of stack-traces is expensive and developers should realise that an exception should only be used in exceptional cases.
Among the worst coding-practices is to ignore or suppress exception with empty catch blocks and continue as nothing had happened. The occurence of a checked exception means that some counter-action is required. A better choice (if an exception does not make sense), is to convert it into an unchecked exception and throw it again.
Just as bad as suppressing an exception is to catch top-level exceptions. I have see plenty of code with a “catch (Exception ex)”. Just remember, that RuntimeException inherits from Exception, thus catching Exception means that also all RuntimeExceptions are caught.
But one of the most annoying (at least when it comes to debugging and problem-resolving) is, that many developers log the same stack-trace more than once, which can cause much confusion.