Exception Handling in Real-Life Java Applications
Exception handling in an application can often be varied, inconsistent, or inappropriate. This article proposes an API to make exception handling more robust and efficient.
Join the DZone community and get the full member experience.
Join For FreeAlthough Java has been out there for almost 20 years, Java developers still facing problems implementing the exception handling in the most proper way, to be re-usable, encapsulated, centralized, and pluggable. In this article, we propose an API design and implementation for exception handling towards making the exception handling more robust and efficient .
Traditional Exception Handling
In traditional exception handling, it is most of the time depends on the seniority level of developers. For example, for junior developers, most likely you will find them either printing the stack trace, showing error message, or worse, they may just eat the exception, like this:
try{
...
}catch(IOException e){
e.printStacktrac();
}
or:
try{
...
}catch(IOException e){
JOptionPane.showMessageDialog(null,”IO Error in page”);
}
Or the worse:
try{
...
}catch(IOException e){
}
For more experienced developers, you may find them wrapping an exception in a runtime exception like this:
try{
...
}catch(IOException e){
throw new RuntimeException(“IO Exception in Class X”,e);
}
Another scenario of wrapping might be seen in a custom business exception, like this:
try{
...
}catch(IOException e){
throw new ERPTechnicalException(“IO Exception in Class X” ,e);
}
Also, one of the common issues can be identified in distinguishing between the usage of try/catch and throws clause across the different tiers and layers of the application.
Exception Handling API
For an advanced and clean exception handling, we propose the following API that provides the following features:
- Reduce the development cost of exception handling (just one single line in a single catch clause).
- Avoid the misunderstanding of try/catch and throws by eliminating the need for throws.
- Make the actual exception handling centralized and implemented by the appropriate people.
- Give developers the ability to plug their exception handling if required.
- Make exception handling consistent across the whole application (e.g. a database down exception should be handled in the same way across all the system views).
- The exception handling shouldn’t affect back-end and API’s when using different front-end technologies (e.g. web or native).
API-Class Diagram
API-Usage by Example
Assume we want to handle FileNotFoundException; in a traditional way, it would be something like this:
package com.jalalkiswani.example;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExampleFileReader {
public void loadFile() throws FileNotFoundException {
FileInputStream in = new FileInputStream("file-not-found.txt");
}
}
package com.jalalkiswani.example;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest {
public static void main(String[] args) {
try {
ExampleFileReader reader=new ExampleFileReader();
reader.loadFile();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
Now, assume this has been called across the whole application (e.g. in 200 classes), and in later phases, the business has changed to handle the exception by showing a Swing message—this would be very costly.
In our proposed API, the developer can handle such exceptions all the time by just calling JKExceptionUtil.handle(e) as follows:
package com.jalalkiswani.example;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import com.jk.exceptions.handler.JKExceptionUtil;
public class ExampleFileReader {
public void loadFile() {
try {
FileInputStream in = new FileInputStream("file-not-found.txt");
System.out.println("Not reachable");
} catch (FileNotFoundException e) {
JKExceptionUtil.handle(e);
}
}
}
In the previous example, the developer doesn't need to handle a specific exception; Rather he/she only catches the Exception class, and calls JKExceptionUtil.handle(e) in the catch clause. In this case, it will call the default exception handler, that also prints the stack trace by default, then throws a runtime exception, wrapping the original exception inside.
Custom Exception Handler
Now, say we want to handle this exception in an appropriate way by showing a Swing message, logging the exception, and then throwing a runtime exception, wrapping the original exception in.
We create a custom handler class that implements the JKExceptionHandler Class with the generic parameter FileNotFoundException, as follows:
package com.jalalkiswani.example;
import java.io.FileNotFoundException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import com.jk.exceptions.handler.JKExceptionHandler;
public class FileNotFoundExceptionHandler implements JKExceptionHandler<FileNotFoundException> {
Logger logger = Logger.getLogger(getClass().getName());
public void handle(FileNotFoundException throwable, boolean throwRuntimeException) {
JOptionPane.showMessageDialog(null, "The file your requested is not found");
logger.log(Level.WARNING, "File not found : ", throwable);
if (throwRuntimeException) {
throw new RuntimeException(throwable);
}
}
}
Next, we'll want to register this handler during application startup in a static initializer as follows:
package com.jalalkiswani.example;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import com.jk.exceptions.handler.JKExceptionHandlerFactory;
import com.jk.exceptions.handler.JKExceptionUtil;
public class ExceptionTest {
static {
JKExceptionHandlerFactory.getInstance().setHandler(FileNotFoundException.class, new FileNotFoundExceptionHandler());
}
public static void main(String[] args) {
ExampleFileReader reader = new ExampleFileReader();
reader.loadFile();
}
}
By registering this exception handler, this handler will be called every time by ExceptionUtil.handle(e) if the exception instance is of type FileNotFoundException.
It's recommended the handlers are set in the application bootstrapping phase, in the main class in the case of native applications (i.e. desktop, console, or mobile), or in context listeners in the case of web and enterprise applications.
Sequence Diagram
The following sequence diagram shows how the API works.
It starts by getting an instance of the exception handler factory, then calls getHandlerInfo for a specific exception; If no handler is registered, the default handler is called, which prints the stack trace by default and then throws a runtime exception wrapping the original exception in.
JK-Util
Since I have been using this technique for a long time, I have uploaded it along with other utility APIs to one of my open-source projects github (JK-Util) as a contribution to the open-source community. Just drop the Maven dependency into your Maven project as follows :
<dependency>
<groupId>com.jalalkiswani</groupId>
<artifactId>jk-util</artifactId>
<version>0.0.9</version>
</dependency>
Future Work
Annotation detection will be useful for automatic registration of exception handlers, which will make it even easier and more robust.
Update - June 27,2016:
I have updated the API to include annotation detection, which will reduce the size and efforts registering the exception handlers.
You can do that by adding @ExceptionHandler annotation to your exception handler classes, then In your application bootstrap register the package that contains the handlers using:
JKExceptionHandlerFactory.getInstance().registerHanders(yourPackageNameString);
and it will automatically scan for all classes annotated with ExceptionHandler.
Updated Example:
package com.jalalkiswani.example;
import java.io.FileNotFoundException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import com.jk.exceptions.handler.ExceptionHandler;
import com.jk.exceptions.handler.JKExceptionHandler;
@ExceptionHandler
public class FileNotFoundExceptionHandler implements JKExceptionHandler<FileNotFoundException> {
Logger logger = Logger.getLogger(getClass().getName());
public void handle(FileNotFoundException throwable, boolean throwRuntimeException) {
JOptionPane.showMessageDialog(null, "The file your requested is not found");
logger.log(Level.WARNING, "File not found : ", throwable);
if (throwRuntimeException) {
throw new RuntimeException(throwable);
}
}
}
package com.jalalkiswani.example;
import com.jk.exceptions.handler.JKExceptionHandlerFactory;
public class ExceptionTest {
static {
JKExceptionHandlerFactory.getInstance().registerHanders("com.jalalkiswani.example");
// JKExceptionHandlerFactory.getInstance().setHandler(FileNotFoundException.class, new FileNotFoundExceptionHandler());
}
public static void main(String[] args) {
ExampleFileReader reader = new ExampleFileReader();
reader.loadFile();
}
}
Be sure to updated maven dependecy to version 0.0.9-1:
<dependency>
<groupId>com.jalalkiswani</groupId>
<artifactId>jk-util</artifactId>
<version>0.0.9-1</version>
</dependency>
Conclusion
In this article we have discussed the issues and limitations of traditional exception handling in Java. In addition we proposed an API design for exception handling that overcomes these limitations and constraints to make exception handling the most appropriate approach for all types of applications.
Acknowledgement
As always, without the support, review, and auditing from my dear friend Dr. Muhanna Muhanna , Assistant Professor at Princess Sumaya University for technology, this article wouldn’t be published with this quality. Thanks Dr.Muhanna.
Jalal Kiswani
"Talk is cheap. Show me the code."
Torvalds, Linus
Opinions expressed by DZone contributors are their own.
Comments