Exceptions are annoying. And they are bothersome to handle. They invade your code like an ant colony and we never know how to deal with them. This small article gives you five best practices on how to handle 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, error management can be implemented either by using an error state or by throwing an exception.
An exception can be used in different scenarios :
- Voluntary exceptions: the coder has detected a situation that may require interrupting program execution (invalid input data by instance).
- SDK/Server exceptions: one of the underlying libraries used by your program is triggering an exception and the coder can handle it.
- Runtime exceptions: an invalid input, a remote resource, or a physical device creates an unexpected behavior instance, causing the program to disrupt its normal flow.
Usually, we have three types of Java exceptions, but they’re also some special cases that exist (InterruptedException by instance) :
- Unchecked exceptions − The unchecked exception classes are run-time exception classes and the error classes.
- Checked exceptions − The checked exception classes are all exception classes except unchecked exception classes.
- Errors − Errors are a subtype of checked exceptions, that extend the Error class.
The Special Cases: some exceptions have a special meaning and need to be handled accordingly, here is a non-exhaustive list :
- NullPointerException: oops, 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 have drawn the panorama of Java exceptions, let’s see some best practices to handle them:
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 a 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 a checked exception is thrown by a method call, the coder usually faces three situations :
- Log the exception and handle it properly: the exception has meaning for the coder and appropriate treatment will be applied. For example, closing some resources or applying a retry.
- Ignore and throws the exception back to the upper stack trace. 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 the program handle it.
Do not handle unrecoverable exceptions
Some situations are unrecoverable: when a program cannot allocate resources, a driver is missing. In such a case, when the exception is associated with such a situation, I would recommend 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.
Next to read SonarQube and ReactJS
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.
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 create your code around concepts, activities, and relationships that are inspired by the business problem you attempt to solve.
You may have a Customer entity, an 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
- CustomerNotFoundExeption 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 a 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 provide default exception messages to simplify the coder’s life.
Handle java exceptions properly
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 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 shows how bad are the Java developers practices:
Do not use Exceptions as Control flow
Last, on 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 replaced 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 on how to deal with exceptions. If you feel lost or some details obscure, a static code analysis platform like Embold can help. It provides detailed explanations in case your exceptions are not treated as they need to be.
Author: By Sylvain Leroy
Comments are closed.