Exception Handling
Exceptions are two types 1.Custom/User Defined Exceptions
2.Predefined Exceptions
1. Never Swallow the Exceptions in Catch Block.
2. Declare specific checked Exceptions that your method can
throw.
3. Don’t catch the Exception class rather catch Specific sub
classes.
4. Never Catch Throwable class.
5. Always correctly wrap the exceptions in custom exceptions
so that stack trace is not lost.
6. Either log the exception or throw it never do the both.
7. Never throw any exception in finally block.
8. Always catch those Exceptions that you can actually
handle.
9. Don’t Use PrintStackTrance() methods or Similar one.
10. Use finally blocks instead of catch blocks if you are
not going to handle the exception.
11. Remember “Throw Early Catch Late” principle.
12. Always Clean up after handling the Exception.
13. Throw only Relevent Exceptions from a method.
14. Never use Exceptions for flow control in your program.
15. Validate User inputs to catch adverse conditions very
early in request processing
16. Always include all information about an exception in
single log message.
17. Pass all relevant information to exceptions to make them
informative as much as possible.
18. Always terminate the thread which it is interrupted.
19. Use template method for repeated try-catch.
20. Document all Exceptions in your application in Javadoc.
there are three general
types of throwable classes in Java: checked exceptions, unchecked exceptions,
and errors.

Checked
exceptions are exceptions that must be declared in the throws clause of
a method. They extend Exception and are intended to be an “in your face” type
of exceptions. Java wants you to handle them because they somehow are dependent
on external factors outside your program. A checked exception indicates an
expected problem that can occur during normal system operation. Mostly these
exception happen when you try to use external systems over network or in file
system. Mostly, the correct response to a checked exception should be to try
again later, or to prompt the user to modify his input.
Unchecked
exceptions are exceptions that do not need to be declared in a throws
clause. JVM simply doesn’t force you to handle them as they are mostly
generated at runtime due to programmatic errors. They extend RuntimeException.
The most common example is a NullPointerException [Quite scary.. Isn't it?]. An
unchecked exception probably shouldn’t be retried, and the correct action
should be usually to do nothing, and let it come out of your method and through
the execution stack. At a high level of execution, this type of exceptions
should be logged.
Errors are serious runtime environment problems that
are almost certainly not recoverable. Some examples are OutOfMemoryError,
LinkageError, and StackOverflowError. They generally crash you program or part
of program. Only a good logging practice will help you in determining the exact
causes of errors.
User
defined custom exceptions
Anytime when user feels that he wants to use its own application
specific exception for some reasons, he can create a new class extending
appropriate super class (mostly its Exception.java) and start using it in
appropriate places. These user defined exceptions can be used in two ways:
1) Either directly throw the custom
exception when something goes wrong in application
throw new DaoObjectNotFoundException("Couldn't find dao
with id " + id);
2) Or wrap the original exception inside
custom exception and throw it
catch (NoSuchMethodException e) {
throw new DaoObjectNotFoundException("Couldn't find
dao with id " + id, e);
}
Wrapping an exception can provide extra information to the user
by adding your own message/ context information, while still preserving the
stack trace and message of the original exception. It also allows you to hide
the implementation details of your code, which is the most important reason to
wrap exceptions.
Now lets start exploring the best practices followed for
exception handling industry wise.
Best
practices you must consider and follow
1) Never swallow the exception
in catch block
1
2
3
|
catch (NoSuchMethodException e) {
return null;
}
|
Doing this not only return “null” instead of handling or
re-throwing the exception, it totally swallows the exception, losing the cause
of error forever. And when you don’t know the reason of failure, how you would
prevent it in future? Never do this !!
2) Declare the specific checked
exceptions that your method can throw
1
2
|
public void foo() throws Exception { //Incorrect way
}
|
Always avoid doing this as in above code sample. It simply
defeats the whole purpose of having checked exception. Declare the specific
checked exceptions that your method can throw. If there are just too many such
checked exceptions, you should probably wrap them in your own exception and add
information to in exception message. You can also consider code refactoring
also if possible.
1
2
|
public void foo() throws SpecificException1,
SpecificException2 { //Correct way
}
|
3) Do not catch the Exception
class rather catch specific sub classes
1
2
3
4
5
|
try {
someMethod();
} catch
(Exception e) {
LOGGER.error("method has failed", e);
}
|
The problem with catching Exception is that if the method you
are calling later adds a new checked exception to its method signature, the
developer’s intent is that you should handle the specific new exception. If
your code just catches Exception (or Throwable), you’ll never know about the
change and the fact that your code is now wrong and might break at any point of
time in runtime.
4)
Never catch Throwable class
Well,
its one step more serious trouble. Because java errors are also subclasses of
the Throwable. Errors are irreversible conditions that can not be handled by
JVM itself. And for some JVM implementations, JVM might not actually even
invoke your catch clause on an Error.
5) Always correctly wrap the
exceptions in custom exceptions so that stack trace is not lost
1
2
3
|
catch (NoSuchMethodException e) {
throw new MyServiceException("Some
information: " +
e.getMessage()); //Incorrect way
}
|
This destroys the stack trace of the original exception, and is
always wrong. The correct way of doing this is:
1
2
3
|
catch (NoSuchMethodException e) {
throw new MyServiceException("Some
information: " ,
e); //Correct way
}
|
6) Either log the exception or
throw it but never do the both
1
2
3
4
|
catch (NoSuchMethodException e) {
LOGGER.error("Some information", e);
throw e;
}
|
As in above example code, logging and throwing will result in
multiple log messages in log files, for a single problem in the code, and makes
life hell for the engineer who is trying to dig through the logs.
7) Never throw any exception
from finally block
1
2
3
4
5
|
try {
someMethod(); //Throws
exceptionOne
} finally
{
cleanUp(); //If
finally also threw any exception the exceptionOne will be lost forever
}
|
This is fine, as long as cleanUp() can never throw any
exception. In the above example, if someMethod() throws an exception, and in
the finally block also, cleanUp() throws an exception, that second exception
will come out of method and the original first exception (correct reason) will
be lost forever. If the code that you call in a finally block can possibly
throw an exception, make sure that you either handle it, or log it. Never let
it come out of the finally block.
8) Always catch only those
exceptions that you can actually handle
1
2
3
|
catch (NoSuchMethodException e) {
throw e; //Avoid this as it doesn't help anything
}
|
Well this is most important concept. Don’t catch any exception
just for the sake of catching it. Catch any exception only if you want to
handle it or, you want to provide additional contextual information in that
exception. If you can’t handle it in catch block, then best advice is just
don’t catch it only to re-throw it.
9)
Don’t use printStackTrace() statement or similar methods
Never
leave printStackTrace() after finishing your code. Chances are one of your
fellow colleague will get one of those stack traces eventually, and have
exactly zero knowledge as to what to do with it because it will not have any
contextual information appended to it.
10) Use finally blocks instead
of catch blocks if you are not going to handle exception
1
2
3
4
5
|
try {
someMethod(); //Method
2
} finally
{
cleanUp(); //do
cleanup here
}
|
This is also a good practice. If inside your method you are
accessing some method 2, and method 2 throw some exception which you do not
want to handle in method 1, but still want some cleanup in case exception
occur, then do this cleanup in finally block. Do not use catch block.
11)
Remember “Throw early catch late” principle
This is
probably the most famous principle about Exception handling. It basically says
that you should throw an exception as soon as you can, and catch it late as
much as possible. You should wait until you have all the information to handle
it properly.
This
principle implicitly says that you will be more likely to throw it in the
low-level methods, where you will be checking if single values are null or not
appropriate. And you will be making the exception climb the stack trace for
quite several levels until you reach a sufficient level of abstraction to be
able to handle the problem.
12)
Always clean up after handling the exception
If you
are using resources like database connections or network connections, make sure
you clean them up. If the API you are invoking uses only unchecked exceptions,
you should still clean up resources after use, with try – finally blocks.
Inside try block access the resource and inside finally close the resource.
Even if any exception occur in accessing the resource, then also resource will
be closed gracefully.
13)
Throw only relevant exception from a method
Relevancy
is important to keep application clean. A method which tries to read a file; if
throws NullPointerException then it will not give any relevant information to
user. Instead it will be better if such exception is wrapped inside custom
exception e.g. NoSuchFileFoundException then it will be more useful for users
of that method.
15)
Validate user input to catch adverse conditions very early in request
processing
Always
validate user input in very early stage, even before it reached to actual
controller. It will help you to minimize the exception handling code in your
core application logic. It also helps you in making application consistent if
there is some error in user input.
For
example: If in user registration application, you are following below logic:
1)
Validate User
2) Insert User
3) Validate address
4) Insert address
5) If problem the Rollback everything
2) Insert User
3) Validate address
4) Insert address
5) If problem the Rollback everything
This is
very incorrect approach. It can leave you database in inconsistent state in
various scenarios. Rather validate everything in first place and then take the
user data in dao layer and make DB updates. Correct approach is:
1)
Validate User
2) Validate address
3) Insert User
4) Insert address
5) If problem the Rollback everything
2) Validate address
3) Insert User
4) Insert address
5) If problem the Rollback everything
16)
Always include all information about an exception in single log message
LOGGER.debug(“Using
cache sector A”);
LOGGER.debug(“Using retry sector B”);
LOGGER.debug(“Using retry sector B”);
Don’t
do this.
Using a
multi-line log message with multiple calls to LOGGER.debug() may look fine in
your test case, but when it shows up in the log file of an app server with 400
threads running in parallel, all dumping information to the same log file, your
two log messages may end up spaced out 1000 lines apart in the log file, even
though they occur on subsequent lines in your code.
Do it
like this:
LOGGER.debug(“Using
cache sector A, using retry sector B”);
17)
Pass all relevant information to exceptions to make them informative as much as
possible
This is
also very important to make exception messages and stack traces useful and
informative. What is the use of a log, if you are not able to determine
anything out of it. These type of logs just exist in your code for decoration
purpose.
18) Always terminate the thread
which it is interrupted
1
2
3
4
5
6
|
while (true) {
try {
Thread.sleep(100000);
} catch (InterruptedException e)
{} //Don't do this
doSomethingCool();
}
|
InterruptedException is a clue to your code that it should stop
whatever it’s doing. Some common use cases for a thread getting interrupted are
the active transaction timing out, or a thread pool getting shut down. Instead
of ignoring the InterruptedException, your code should do its best to finish up
what it’s doing, and finish the current thread of execution. So to correct the
example above:
1
2
3
4
5
6
7
8
|
while (true) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
break;
}
}
doSomethingCool();
|
19) Use template methods for
repeated try-catch
There is no use of having a similar catch block in 100 places in
your code. It increases code duplicity which does not help anything. Use
template methods for such cases.
For example below code tries to close a database connection.
1
2
3
4
5
6
7
8
9
|
class DBUtil{
public static void closeConnection(Connection
conn){
try{
conn.close();
} catch(SQLException ex){
throw new RuntimeException("Cannot
close connection", ex);
}
}
}
|
This type of method will be used in thousands of places in your
application. Don’t put whole code in every place rather define above method and
use it everywhere like below:
public void dataAccessCode()
{
Connection conn = null;
try{
conn = getConnection();
....
} finally{
DBUtil.closeConnection(conn);
}
|
20)
Document all exceptions in your application in javadoc
Make it
a practice to javadoc all exceptions which a piece of code may throw at
runtime. Also try to include possible course of action, user should follow in
case these exception occur.
Nature of Exceptions : 3 types
- Exceptions due to programming errors: In this category, exceptions are generated due to
programming errors (e.g.,NullPointerException andIllegalArgumentException).
The client code usually cannot do anything about programming errors.
- Exceptions due to client code errors: Client code attempts something not allowed by the
API, and thereby violates its contract. The client can take some
alternative course of action, if there is useful information provided in
the exception. For example: an exception is thrown while parsing an XML
document that is not well-formed. The exception contains useful
information about the location in the XML document that causes the
problem. The client can use this information to take recovery steps.
- Exceptions due to resource failures: Exceptions that get generated when resources
fail. For example: the system runs out of memory or a network connection
fails. The client's response to resource failures is context-driven. The
client can retry the operation after some time or just log the resource
failure and bring the application to a halt.
Types
of Exceptions in Java
Java
defines two kinds of exceptions:
·
Checked exceptions: Exceptions that inherit from the
Exception class
are checked exceptions. Client code has to handle the checked exceptions thrown
by the API, either in a catch clause or by forwarding it outward with the throws clause.
·
Unchecked exceptions:
By
way of example, Figure 1 shows the hierarchy for RuntimeException also
extends from Exception.
However, all of the exceptions that inherit from RuntimeException get
special treatment. There is no requirement for the client code to deal with
them, and hence they are called unchecked exceptions.NullPointerException.
Figure 1. Sample exception hierarchy
In this diagram,
NullPointerException extends from RuntimeException and
hence is an unchecked exception.
A
checked exception thrown by a lower layer is a forced contract on the invoking
layer to catch or throw it. The checked exception contract between the API and
its client soon changes into an unwanted burden if the client code is unable to
deal with the exception effectively. Programmers of the client code may start
taking shortcuts by suppressing the exception in an empty catch block or just
throwing it and, in effect, placing the burden on the client's invoker.
Checked
exceptions are also accused of breaking encapsulation. Consider the following:public List getAllAccounts() throws
FileNotFoundException, SQLException{
...
The method}
getAllAccounts() throws two checked exceptions. The client
of this method has to explicitly deal with the implementation-specific
exceptions, even if it has no idea what file or database call has failed within getAllAccounts(), or has no business providing
filesystem or database logic. Thus, the exception handling forces an
inappropriately tight coupling between the method and its callers.
Best
Practices for Designing the API
Having
said all of this, let us now talk about how to design an API that throws
exceptions properly.
1. When deciding on checked
exceptions vs. unchecked exceptions, ask yourself, "What action can the
client code take when the exception occurs?"
If
the client can take some alternate action to recover from the exception, make
it a checked exception. If the client cannot do anything useful, then make the
exception unchecked. By useful, I mean taking steps to recover from the
exception and not just logging the exception. To summarize:
Client's reaction when exception happens
|
Exception type
|
Client code
cannot do anything
|
Make it an
unchecked exception
|
Client code
will take some useful recovery action based on information in exception
|
Make it a
checked exception
|
NullPointerException, IllegalArgumentException, and IllegalStateException. I prefer working with
standard exceptions provided in Java rather than creating my own. They make my
code easy to understand and avoid increasing the memory footprint of code.
2. Preserve encapsulation.
Never
let implementation-specific checked exceptions escalate to the higher layers.
For example, do not propagate SQLException from data access code to the business
objects layer. Business objects layer do not need to know about SQLException. You have two options:
·
Convert
SQLException into another checked exception, if the
client code is expected to recuperate from the exception.
·
Convert
Most
of the time, client code cannot do anything about SQLException into an unchecked exception, if the client
code cannot do anything about it.SQLExceptions. Do not hesitate to convert
them into unchecked exceptions. Consider the following piece of code:public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
}
This}
catch block
just suppresses the exception and does nothing. The justification is that there
is nothing my client could do about an SQLException. How
about dealing with it in the following manner?public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
This converts}
SQLException to RuntimeException. If SQLException occurs,
the catch clause throws a new RuntimeException. The execution thread is
suspended and the exception gets reported. However, I am not corrupting my
business object layer with unnecessary exception handling, especially since it
cannot do anything about an SQLException. If my catch needs
the root exception cause, I can make use of the getCause() method
available in all exception classes as of JDK1.4.If you are confident that the business layer can take some recovery action when
SQLException occurs,
you can convert it into a more meaningful checked exception. But I have found
that just throwingRuntimeException suffices most of the time.
3. Try not to create new custom
exceptions if they do not have useful information for client code.
What
is wrong with following code?public class DuplicateUsernameException
It is not giving any useful information to the client code, other than an indicative exception name. Do not forget that Javaextends Exception {}
Exception classes
are like other classes, wherein you can add methods that you think the client
code will invoke to get more information.We could add useful methods to
DuplicateUsernameException, such
as:public class DuplicateUsernameException
extends Exception {
public DuplicateUsernameException
(String username){....}
public String requestedUsername(){...}
public String[] availableNames(){...}
The new version provides two useful methods:}
requestedUsername(), which returns the requested
name, and availableNames(), which
returns an array of available usernames similar to the one requested. The
client could use these methods to inform that the requested username is not
available and that other usernames are available. But if you are not going to
add extra information, then just throw a standard exception:Even better, if you think the client code is not going to take any action other than logging if the username is already taken, throw a unchecked exception:throw new Exception("Username already taken");
Alternatively, you can even provide a method that checks if the username is already taken.throw new RuntimeException("Username already taken");
It is worth repeating that checked exceptions are to be used in situations where the client API can take some productive action based on the information in the exception. Prefer unchecked exceptions for all programmatic errors. They make your code more readable.
4. Document exceptions.
You
can use Javadoc's @throws tag to document both checked and unchecked
exceptions that your API throws. However, I prefer to write unit tests to
document exceptions. Tests allow me to see the exceptions in action and hence
serve as documentation that can be executed. Whatever you do, have some way by
which the client code can learn of the exceptions that your API throws. Here is
a sample unit test that tests forIndexOutOfBoundsException:public void testIndexOutOfBoundsException() {
ArrayList blankList = new ArrayList();
try {
blankList.get(10);
fail("Should raise an IndexOutOfBoundsException");
} catch (IndexOutOfBoundsException success) {}
The code above should throw an}
IndexOutOfBoundsException when blankList.get(10) is invoked. If it does not, the fail("Should raise an IndexOutOfBoundsException") statement explicitly fails the test. By
writing unit tests for exceptions, you not only document how the exceptions
work, but also make your code robust by testing for exceptional scenarios.
Best
Practices for Using Exceptions
The
next set of best practices show how the client code should deal with an API
that throws checked exceptions.
1. Always clean up after
yourself
If
you are using resources like database connections or network connections, make
sure you clean them up. If the API you are invoking uses only unchecked
exceptions, you should still clean up resources after use, withtry - finally blocks.public void dataAccessCode(){
Connection conn = null;
try{
conn = getConnection();
..some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
} finally{
DBUtil.closeConnection(conn);
}
}
class DBUtil{
public static void closeConnection
(Connection conn){
try{
conn.close();
} catch(SQLException ex){
logger.error("Cannot close connection");
throw new RuntimeException(ex);
}
}
}
DBUtil is a utility class that closes the Connection. The important point is the use of finally block,
which executes whether or not an exception is caught. In this example, the finally closes
the connection and throws a RuntimeException if there is problem with closing the
connection.
2. Never use exceptions for
flow control
Generating
stack traces is expensive and the value of a stack trace is in debugging. In a
flow-control situation, the stack trace would be ignored, since the client just
wants to know how to proceed.In the code below, a custom exception,
MaximumCountReachedException, is
used to control the flow.public void useExceptionsForFlowControl() {
try {
while (true) {
increaseCount();
}
} catch (MaximumCountReachedException ex) {
}
//Continue execution
}
public void increaseCount()
throws MaximumCountReachedException {
if (count >= 5000)
throw new MaximumCountReachedException();
The}
useExceptionsForFlowControl() uses an infinite loop to increase the count
until the exception is thrown. This not only makes the code difficult to read,
but also makes it slower. Use exception handling only in exceptional
situations.
3. Do not suppress or ignore
exceptions
When
a method from an API throws a checked exception, it is trying to tell you that
you should take some counter action. If the checked exception does not make
sense to you, do not hesitate to convert it into an unchecked exception and
throw it again, but do not ignore it by catching it with {} and then
continue as if nothing had happened.
4. Do not catch top-level
exceptions
Unchecked
exceptions inherit from the RuntimeException class, which in turn inherits from Exception. By catching the Exception class,
you are also catching RuntimeException as in the following code:try{
..
}catch(Exception ex){
The code above ignores unchecked exceptions, as well.}
5. Log exceptions just once
Logging
the same exception stack trace more than once can confuse the programmer
examining the stack trace about the original source of exception. So just log
it once.
Most
of the time, client code cannot do anything about SQLExceptions. Do not
hesitate to convert them into unchecked exceptions. Consider the following
piece of code:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
}
}
This
catch block just suppresses the exception and does nothing. The justification
is that there is nothing my client could do about an SQLException. How about
dealing with it in the following manner?
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
This
converts SQLException to RuntimeException. If SQLException occurs, the catch
clause throws a new RuntimeException. The execution thread is suspended and the
exception gets reported. However, I am not corrupting my business object layer
with unnecessary exception handling, especially since it cannot do anything
about an SQLException. If my catch needs the root exception cause, I can make
use of the getCause() method available in all exception classes as of JDK1.4.
Throwing
checked exceptions and not being able to recover from it is not helping.
Some people even think that checked exceptions should not be
used at all. Seehttp://www.ibm.com/developerworks/java/library/j-jtp05254/index.html
Recently,
several well-regarded experts, including Bruce Eckel and Rod Johnson, have
publicly stated that while they initially agreed completely with the orthodox
position on checked exceptions, they've concluded that exclusive use of checked
exceptions is not as good an idea as it appeared at first, and that checked
exceptions have become a significant source of problems for many large
projects. Eckel takes a more extreme view, suggesting that all exceptions
should be unchecked; Johnson's view is more conservative, but still suggests
that the orthodox preference for checked exceptions is excessive. (It's worth
noting that the architects of C#, who almost certainly had plenty of experience
using Java technology, chose to omit checked exceptions from the language
design, making all exceptions unchecked exceptions. They did, however, leave
room for an implementation of checked exceptions at a later time.)
Also
from the same link:
The
decision to use unchecked exceptions is a complicated one, and it's clear that
there's no obvious answer. The Sun advice is to use them for nothing, the C#
approach (which Eckel and others agree with) is to use them for everything.
Others say, "there's a middle ground."
Author: Sahitya Mittapalli
No comments:
Post a Comment