5 Best practices to handle your exceptions in Java
Exceptions are annoying. The exceptions are bothersome to handle, it invades your code like a ant colony and we never know how to deal with them. This small article gives you 5 best practices to dead with exceptions in your Java code.
Exceptions in Java
An exception is triggered when a problem arises during the execution of a program.The normal flow of the program will be interrupted and your program or your web request ( in the case of a server) may end abnormally.
Usually, the error management can be implemented either by using an error state or by throwing an exception.
An exception can be using in different scenarios :
- Voluntary exceptions : the coder has detected a situation that may requires to interrupt the program execution ( invalid input data by instance).
- SDK/Server exceptions : one of the underlying library used by your program is triggering an exception and the coder shall handle it
- Runtime exceptions : an invalid input, a remote resource or a physical device creates an unexpected behavior causing the program to disrupt its normal flow.
Usually we have three type of exceptions but it also exists some special cases ( InterruptedException by instance) :
- Unchecked exceptions − The unchecked exception classes are the run-time exception classes and the error classes.
- Checked exceptions − The checked exception classes are all exception classes other than the unchecked exception classes.
- Errors − The errors are a sub-type of checked exceptions that extends the Error class
Exceptions : hierarchy\
The special cases, some exceptions have a special meaning and need to be handled appropriately, here is a non exhaustive list :
- NullPointerException : oups, an invalid data has caused the NPE, check your code rather than catching this exception
- InterruptedException : a thread has been interrupted, you have to close your opened resources and re throw the exception
- NoSuchElementException : employed in iterators
- …
Now that we drew the panorama of the Java exceptions, let’s see some good sense best practices :
Avoid Checked exceptions as much as possible
Some exceptions are very serious. For example, a program might attempt to allocate some memory when no free memory is available. Or open an nonexistent file.
However beyond the original wish to build more robust software ( James Gosling), the checked exceptions have been misused and present several drawbacks.
The checked exceptions are everywhere. When an checked exception is thrown by a method call, the coder usually faces three situations :
- Log the exception and handle it properly : the exception has a meaning for the coder and an appropriate treatment will be applied. For example closing some resources or applying a retry.
- Ignore and throws the exception back to the upper stacktrace. Just add
throws MyCheckedException
to my method signature et voila. - Hide the exception and wrap it as a Runtime Exception : When you cannot modify the signature of your method or when you are using a lambda expression, a trick is to wrap the CheckedException in RuntimeException. It allows you to do a “fire and forget”. Let’s the program handle it.
try {
// do stuff
} catch (AnnoyingcheckedException e) {
throw new RuntimeException(e);
}
Do not handle unrecoverable exceptions
Some situations are unrecoverable : when a program cannot allocate resources, a driver is missing. In such case, when the exception is associated to such situation, I would recommend to not deal with it.
What does it mean ? Errors and exceptions that cannot be handled should be lifted upward in the program flow to fail as fast as possible.
As related in the publication “Analysis of Exception Handling Patterns in Java”, most Java developers are killing exception noise using the trick Exception -> RuntimeException conversion.
Wrapping Exceptions : Analysis of Exception Handling Patterns in Java\
As a rule of thumb, catching unrecoverable exceptions and adding a plain log won’t bring any real value to your code.
Creates functional exceptions
Have you heard about DDD and the domain concept ? When you are designing your code, you usually creates your code around concepts, activities and relationships that are inspired from the business problem you attempt to solve.
You may have a Customer entity, a Order and Invoice objects. What is the name of the hypothetical exception thrown when an Invoice creation command is received by your program and the customer is no more existing ?
IllegalArgumentException ( message = "Customer does not exist")
NullPointerException
: the case has been not designed :-DCustomerNotFoundExeption
the exception is self describing.
Functional exceptions offer several advantages to the standard exceptions provided in the JDK. They are self-described by their name, and they may contain an valuable business context to understand the source of the error. It also allows the coder to map these exceptions to HTTP Code ( here a 400) using an Exception Resolver like with Jersey or Spring. And you may also provides default exception messages to simplify the coder’s life.
Handle Properly Exceptions
When you are writing your catch statements, keep in mind to :
- close the open resources : streams, files, sockets. Everything that is under the class / component responsibility should be closed properly. Otherwise your program may face a resource exhaustion.
- delete temporary resources : temporary files should be deleted
- Marks the transaction as rollback : unless you are using AOP and @Transactional annotations, do not forget to mark your transactions for a rollback
- Unless you are certain that the program may resume its execution, re-throws the exception. Do not forget to wrap it in a more meaningful way.
- Provides a meaning logging, do not use printStackTrace etc. A recent study of GITHub show how bad are the Java developers practices :
Do not use Exceptions as Control flow
Last of the list, this advice seems a bit unreal. I seldom encountered code from developers using this trick. However younger developers are often tempted to use it and it is so wrong.
The trick is to use the exception as a return value and applying a certain behavior like in this example :
try {
for (int i = 0; /*wot no test?*/ ; i++)
array[i]++;
} catch (ArrayIndexOutOfBoundsException e) {}
You can find more details in the excellent article here.
Here the developer is using the exception thrown at runtime by the JVM to exit the loop and resume its program execution.
The execution will be slow, and plus the mind bog it represents, it is absolutely inefficient and can be replace by a loop condition.
So please, don’t, unless you really know what you are doing.
Conclusion
In this quick blog article, we reviewed several best practices how to deal with the exceptions. If you feel lost or some details obscure, I advise you to use a static analysis tool like Embold. This next level static analyzer provides nice explanations when a situation appear where your exceptions are not treated as they deserved to be.
Embold and handling your exceptions\