Concentrate on the Java Exceptions that Matter to Your Application

xceptions, indicators of abnormal conditions that disrupt the normal flow of a program, are a prominent fixture of the Java language. You have to handle exceptions to write a useful Java program.

Java classifies exceptions as either checked or unchecked and, as with everything else, represents them as objects. Checked exceptions extend java.lang.Exception, and they must be handled by a try/catch block or a throws clause for compilation to proceed. Unchecked exceptions extend java.lang.RuntimeException, and they are not checked by the compiler.

Although extremely helpful, Java’s checked exceptions mechanism has an unwanted side effect: the “try/catch/do nothing” code block:

try{    // Some code that generates checked exceptions}catch(Exception e){    // do nothing}

Many articles have warned against the problems this coding practice causes and encouraged developers to avoid it. Yet it is still prevalent in Java code. Aside from bad programming, a main reason for this practice is that developers don’t know what to do with generated exceptions: some Java APIs generate more than one exception and how to handle them or which code to write in the catch code block is not always clear.

The easiest solution is to delegate responsibility for exception handling to other parts of an application using the methods you wrote: simply throw exceptions that you don’t want to handle. In n-tiered applications, however, this means an exception could be thrown again and again until it ends up in a “try/catch/do nothing” block. If you write a method that throws a java.sql.SQLException because you don’t know what to do with the exception, then most certainly other developers using your method won’t know how to handle it either.

This article presents an easy way to relieve Java developers from worrying about what to do with exceptions that don’t relate directly to their applications, allowing them to concentrate on handling only the exceptions that affect the flow of their applications.

A Classic Example: Getting a Database Connection

When developing Java applications that interact with a relational database, you use the JDBC (Java database connectivity) API. The first step is always getting a database connection object (java.sql.Connection). Usually, the code required to get a connection object is placed in one method that other methods performing CRUD (Create, Read, Update, Delete) operations on a database use. Listing 1 shows how to write such a method. The code displays an Oracle database connection string, but it could be any database.

Listing 1: How a Database Connection Is Usually Retrieved
public static Connection getConnection() throws ClassNotFoundException, SQLException { Connection connection = null; // Load the JDBC driver for your database String driverName = "oracle.jdbc.driver.OracleDriver"; Class.forName(driverName); // Create a connection to the database, usually those values are retrieved from a properties file String serverName = "127.0.0.1"; String portNumber = "1521"; String sid = "mydatabase"; String url = "jdbc:oracle:thin:@" + serverName + ":" + portNumber + ":" + sid; String username = "username"; String password = "password"; connection = DriverManager.getConnection(url, username, password); return connection; }

The problem with using this version of the getConnection() method is that you have to handle java.lang.ClassNotFoundException and java.sql.SQLException, and you can’t do much except throw these exceptions again. From your application’s perspective, either of them prevents a database connection from being retrieved.

An updated version of the getConnection() method avoids your having to handle those exceptions (see Listing 2). This version eases the burden of handling exceptions whenever getConnection() is called by not throwing exceptions. However, returning null creates unexpected runtime behavior.

Listing 2: An Updated Version of the getConnection() Method
public static Connection getConnection() { Connection connection = null; try { // Load the JDBC driver for your database String driverName = "oracle.jdbc.driver.OracleDriver"; Class.forName(driverName); // Create a connection to the database, usually those values are retrieved from a properties file String serverName = "127.0.0.1"; String portNumber = "1521"; String sid = "mydatabase"; String url = "jdbc:oracle:thin:@" + serverName + ":" + portNumber + ":" + sid; String username = "username"; String password = "password"; connection = DriverManager.getConnection(url, username, password); return connection; } catch (ClassNotFoundException e) { // Could not find the database driver } catch (SQLException e) { // Could not connect to the database } // return null in case of any exception return null; }

A Business Classification for Java Exceptions

Looking back at Listing 1, the two exceptions thrown there are the kind of exceptions that should not occur once development is over. In fact, when such exceptions occur while your application is in production, your application cannot recover from them. An administrator must intervene if it is a database or network issue, otherwise a Java programmer has to get involved. This means all the code you wrote to handle those exceptions is of little practical value.

This leads to the classification of exceptions from an application development view:

  1. Application exceptions?Exceptions that are related to the application’s business and must be handled properly
  2. System exceptions?Exceptions resulting from technical issues, whether they are programming-related or infrastructure-related (database or network)

Application exceptions are usually meaningful to the end user. When they are handled, a proper message (depending on the business requirement) is displayed, telling the application user what went wrong and what he/she can do to recover from it (like changing the data he/she entered).

As an example, consider a “user registration” page containing a field to enter user name, which must be unique. This is usually done by applying a database unique constraint to a table column. When the user enters an existing user name, a java.sql.SQLException that wraps the unique constraint violation message produced by the RDBMS is generated. You are required to return an appropriate message to the user in this case.

System exceptions cannot be recovered from by the end user; they can be handled only by technical staff. For example, if the database fails or there is a programming error, an end user can do nothing about it. System exceptions are unlikely to occur once your application reaches a stable state.

Mapping Business Exceptions to Java’s Exceptions

Application and system exceptions can be mapped to Java exceptions using the following two custom exceptions:

  1. ApplicationException (see Listing 3): All exceptions that make sense from a business perspective are wrapped in this exception. It extends java.lang.Exception, which means that it has to be handled by the developer with a proper try/catch. These are the exceptions that a developer should worry about. When you develop a method that throws ApplicationException, developers are forced to handle this exception.
    Listing 3: ApplicationException Has Error Codes Representing
    Common Database Operations Problems
    public class ApplicationException extends Exception { public static final int UNIQUE_KEY = 1; public static final int ROW_NOT_FOUND = 2; private int code; public ApplicationException( int code ) { this.code= code; } public ApplicationException( String msg ) { super( msg ); } public ApplicationException( int code , String msg ) { super( msg ); this.code= code; } public int getCode() { return code; } }
  2. SystemException (see Listing 4): This exception extends java.lang.RuntimeException. All exceptions classified as system exceptions will be replaced by this exception. Because it extends java.lang.RuntimeException, handling it is not forced by the compiler: you handle it wherever it is suitable in your application. Please note that SystemException stores the original Exception, which can be used later on for further information.
    Listing 4: SystemException Stores the Root Cause of the Problem
    public class SystemException extends RuntimeException { public SystemException( Throwable cause ) { super( cause ); }}

Using and Handling SystemException

For demonstration, Listing 5 uses SystemException to rewrite the getConnection() method in Listing 2. The method does not throw checked exceptions, so it can be used without worrying about those exceptions and without a “try/catch/do nothing” block.

Listing 5: getConnection Method Updated to Use SystemException
public static Connection getConnection() { Connection connection = null; try { // Load the JDBC driver for your database String driverName = "oracle.jdbc.driver.OracleDriver"; Class.forName(driverName); // Create a connection to the database, usually those values are retrieved
from a properties file String serverName = "127.0.0.1"; String portNumber = "1521"; String sid = "mydatabase"; String url = "jdbc:oracle:thin:@" + serverName + ":" + portNumber + ":" + sid; String username = "username"; String password = "password"; connection = DriverManager.getConnection(url, username, password); return connection; } catch (ClassNotFoundException e) { throw new SystemException( e ); } catch (SQLException e) { throw new SystemException( e ); } }

Since SystemException is a runtime exception, it will move up the exception stack until it gets handled. Depending on your application, you can pick a suitable point in your code to handle it. For example, if you are developing a Web application based on MVC, you can do a try/catch for SystemException in the controller. Since such exceptions require technical intervention, you can do some logging in the catch for this exception or send an email to the administrator.

An alternative for a Web application would be declaring an exception handler in the web.xml as shown in Listing 6.

Listing 6: Error Pages Are Commonly Used in Web Applications
"http://java.sun.com/dtd/web-app_2_3.dtd"> . . . SystemException sys_exc_error.jsp . . .

Using and Handling ApplicationException

Working with ApplicationException requires more coding: you need to translate the generated exception into a meaningful exception for others using the method you developed.

Going back to the user registration example, Listing 7 shows how to create an ApplicationException when the database unique constraint is violated. The method checks against the error message generated by an Oracle database, but this can be modified for any database.

Listing 7: Using ApplicationException
public static void registerUser() throws ApplicationException { try { Connection connection= Class1.getConnection3(); String sql= "INSERT INTO USERS (USER_NAME,PASSWORD) VALUES (?, ?) "; PreparedStatement stmt= connection.prepareStatement(sql); stmt.setString ( 1 , "test" ); stmt.setString ( 2 , "123" ); stmt.executeUpdate(); } catch( SQLException sqe ) { if ( sqe.getMessage().indexOf("ORA-00001") != -1 ) { // This SQLException is meangful to my application business. throw new ApplicationException( ApplicationException.UNIQUE_KEY ) ; } else { // This SQLException is meangless to my application business, it indicates // a programmer mistake or infrastructure problems throw new SystemException( sqe ); } } }

As you can see, ApplicationException contains constants representing common database errors. You can customize the error codes according to your need.

Listing 7 also shows how a java.sql.SQLException can be treated as either an ApplicationException or a SystemException depending on which makes sense from an application perspective. Other methods using this method will have to handle ApplicationException and act according to the error code it has.

Concentrate on Meaningful Exceptions

By using the technique described in this article, you concentrate on handling only the exceptions that are meaningful to your application. The number of try/catch blocks is also reduced, and you definitely need fewer lines of code.

You also can customize ApplicationException and SystemException to meet your requirements?you can even extend them, if necessary.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts