I guess exception handling is a more advanced concept of programming, and something not really taught at universities. Either way, I found that most developers (novices as well as experts) either do not understand exceptions, or they do not respect it.

I am not going to explain the details of what an exception is, for that I'll refer you to excellent books and articles on the subject. Rather, I will assume you have a basic working knowledge about exceptions and will try to explain the more practical issues here. Firstly, consider the following piece of Java code:

public int f(String pDatabaseName) {
  int vCount = 0;
  ConnectionManager vConMgr = ConnectionManager.getInstance();
  Connection vCon = vConMgr.getConnection(pDatabaseName);
  Statement vStmt = vCon.createStatement();

  ResultSet vRes = vStmt.executeQuery("SELECT * FROM some_table");
  while (vRes.next()) {
    do_something();
    vCount++;
  }

  vRes.close();
  vStmt.close();
  vConMgr.freeConnection(pDatabaseName,vCon);
  vConMgr.release();

  return vCount;
}

Depending on your level of expertise, you will either think nothing is wrong with this code, or that there is a terrible flaw. Consider the normal flow of code. We start by retrieving an instance of the singleton ConnectionManager. Next, we use this instance to retrieve a Connection object from the pool of cached connections. We then create a Statement, which we use to execute the SQL query on and lastly we step through the ResultSet. After we retrieved all the results, we close and release all the resources we allocated in the beginning. If no abnormal situation occurs, this code will work perfectly. However, this is most certainly not always the case. I can think of many possible scenarios - the database server might be down, thereby throwing an exception in the vConMgr.getConnection(pDatabaseName) call, the SQL query might fail, thereby throwing an exception in the ResultSet vRes = vStmt.executeQuery("SELECT * FROM some_table") call, etc. I have seen many, many examples of a first try at fixing this looking something like this:

public int f(String pDatabaseName) {
  int vCount = 0;
  ConnectionManager vConMgr = null;
  Connection vCon = null;

  try {
    try {
      vConMgr = ConnectionManager.getInstance();
      vCon = vConMgr.getConnection(pDatabaseName);

      Statement vStmt = vCon.createStatement();

      ResultSet vRes = vStmt.executeQuery("SELECT * FROM some_table");
      while (vRes.next()) {
        do_something();
        vCount++;
      }

      vRes.close();
      vStmt.close();
    }
    finally {
      vConMgr.freeConnection(pDatabaseName,vCon);
      vConMgr.release();
    }
  }
  catch (Exception e) {
  }

  return vCount;
}

This is actually worse code than the original version. What happens when the call to vConMgr = ConnectionManager.getInstance() fails? Assuming an exception is thrown, the finally clause will be executed. Note that initially vConMgr was set to null, and when an exception is thrown in a method (in this case ConnectionManager.getInstance(), execution will resume in the innermost try-finally or try-catch block - and no assignment will take place to vConMgr. Thus, in the finally block, the first statement to be executed is vConMgr.freeConnection(pDatabaseName,vCon). But vConMgr is null, hence the call is actually null.freeConnection(pDatabaseName,vCon) - which is more than bad. You should never invoke a method on a null object. This will force a NullPointerException to be thrown, and that will cause execution to resume in the try-catch clause. In this exception handler, the exception object is not manipulated but silently suppressed. Execution will now resume at the line return vCount, returning the value 0. In itself this will not really break anything, but the calling method will have no idea that this method failed to execute successfully, there will be no way in which to determine why there was an error (because the exception is suppressed) even if you knew it failed, and in general the real error would be hidden. Even if a stack trace printout was to be made in the exception handler, the error would show as a java.lang.NullPointerException, and not the real reason - an exception in returning an instance pointer to the database connection manager. The reason is obvious - the real error caused another error because it was not correctly handled, and it is always the last error that will be detected, not the first one. These kind of errors are extremely difficult to trace because the real error is hidden.

What happens when no connection object could be returned instead? An exception will be thrown by vCon = vConMgr.getConnection(pDatabaseName), and by following the same reasoning as previously, the try-finally clause will be executed causing the call to vConMgr.freeConnection(pDatabaseName,vCon) to try and free a null connection, because vCon is null. This will again suppress the original exception and cause a phantom exception to be displayed if the appropriate code is added.

What will happen if an exception is thrown by the call to ResultSet vRes = vStmt.executeQuery("SELECT * FROM some_table")? Execution would jump to the try-finally block, where everything would work fine except of course the suppression of the exception in the empty try-catch block. However, if you take a closer look at the code, you will realize that the statement vStmt.close() will not be executed, thus we have a resource leak. In Java the garbage collector would reclaim the memory allocated to the vStmt object, but the statement would not necessarily be closed properly (I know of many broken JDBC implementations in which you need to explicitly close the statement). Given enough of these errors, and one day you will find you are out of resources and thus the whole system will come tumbling down.

To illustrate the concept of Unexpected Exception Transformations, take a look at the following simple class:

import java.lang.*;

class secondary_exception {

  public int f() throws Exception {
    int vCount = 0;
    String vStr = null;

    try {
      try {
        if (1==1)
          throw new Exception("This is the original exception");

        vCount++;
      }
      finally {
        vStr.indexOf("hi");
      }
    }
    catch (Exception e) {
      throw e;
    }

    return vCount;
  }

  public static void main(String[] args) {
    secondary_exception vSE = new secondary_exception();

    try {
      vSE.f();
    }
    catch (Exception e) {
      System.out.println("Exception: "+e.getMessage());
      e.printStackTrace();
    }
  }
}

In the method f(), we throw an exception to simulate a problem like a failed database connection, network failure etc. This exception will be caught by the innermost try-finally block, the line vStr.indexOf("hi") will be executed, then execution will jump to the innermost try-catch block and simply be re-thrown. In the main() method, this exception will be caught once again and simply displayed on screen. Here is a sample of the output:

waldo@waldonbl Play $ java secondary_exception
Exception: null
java.lang.NullPointerException
    at secondary_exception.f(secondary_exception.java:17)
    at secondary_exception.main(secondary_exception.java:31)

The e.getMessage() returned the familiar useless message null, and the stack trace pointed to line 17 of secondary_exception.java, which is the line vStr.indexOf("hi"). The original exception caused another exception because in the process of handling that exception, a bug in the code caused another exception to occur and all information on the initial exception is lost. The reason is obvious - vStr is a null object and we can't call a method on a null object. This is exactly the same concept as that of the previous example, this time only with a simpler example. The big concern this causes is that we can't find the original problem without having to fix a host of other bugs. In essence, this situation could be prevented all together by a full understanding of how to use try-finally and try-catch blocks.

To revisit our first example, consider an improved version:

public int f(String pDatabaseName) throws Exception {
  int vCount = 0;
  ConnectionManager vConMgr = null;
  Connection vCon = null;

  try { // This try-catch handles any exception that occurs in the try-catch block
    vConMgr = ConnectionManager.getInstance();

    try { // This try-finally makes sure vConMgr.release() gets called
      vCon = vConMgr.getConnection(pDatabaseName);

      try { // This try-finally will make sure
            //  vConMgr.freeConnection(pDatabaseName,vCon) gets called
        Statement vStmt = vCon.createStatement();
        if (vStmt == null)
          throw new Exception("Internal Error: vStmt is null in XX.f()");

        try { // This try-finally will make sure vStmt.close() gets called
          ResultSet vRes = vStmt.executeQuery("SELECT * FROM some_table");

          try {  // This try-finally will make sure vRes.close() gets called
            while (vRes.next()) {
              do_something();
              vCount++;
            }
          }
          finally {
            vRes.close();
          }
        }
        finally {
          vStmt.close();
        }
      }
      finally {
        vConMgr.freeConnection(pDatabaseName,vCon);
      }
    }
    finally {
      vConMgr.release();
    }
  }
  catch (Exception e) {
    // Handle the exception
    // In this case, we simply throw it out of the current method
    // because we can't handle it.  Normally we would not catch it at all
    // if we can't handle it
    throw e;
  }

  return vCount;
}

Can you see that there are now many more nested try-finally blocks, because each one is only able to protect one resource? Also, the try-finally block always starts just after the resource was allocated/created. Therefore, if the allocation fails with an exception, the outer try block will catch it and therefore no attempt will be made to free the unallocated resource. Only if the allocation succeeded and the try-finally block is entered, will any subsequent exceptions cause the resource to be released in the finally clause. Also remember that all the finally clauses will be called starting from the block the exception occurred in, to the innermost try-catch clause (which is usually after the outermost try-finally block). If however the allocation does not throw an exception on failure, but rather returns a null or similar "error" value, then that resource should be checked for a valid value and an exception should be raised if not valid, just like it was done for the allocation of the vStmt object. Note that the exception must be thrown on error before the try-finally block is entered that protects that resource.

I have seen many examples of code where null values are returned on error conditions, or other "special" values indicating failure. There is in principle nothing wrong with this, however it is most of the time not a very good method to use. On simple functions returning boolean values, it is okay to return say False on invalid data. As an example, consider the following method:

bool isValidValue(int pValue) {
  if (pValue > 0 && pValue <= 100)
    return true;
  else
    return false;
}

It is clear that the best way to indicate an invalid value here is to return false. However, there are many, many other circumstances where it is much better to throw an exception on error conditions. An example follows:

boolean isValidMSISDN(String pValue) throws Exception {
  if (pValue == null)
    throw new Exception("Internal Error: pValue is null in method "+
                        "X.isValidMSISDN()");

  if (doSomeChecks(pValue))
    return true;
  else
    return false;
}

In this case, a null value for a parameter is usually an indication of a programming bug made somewhere else. If you want to test a number to see whether it is a valid MSISDN, it is logical to assume you want to give it something to test. But when this code gets a null value, it is usually a very good indication of a lurking bug somewhere in the caller. If the code returned false, then this possible situation would not be detected. However, shouting like this on invalid data will only help find possible bugs much more quickly. Note that there is a difference between invalid data and an invalid value. Invalid data refer to a value that is not a valid instance of the representing data type, whereas an invalid value refers to a valid instance of the representing data type, but an invalid value in the domain set for that value. In other words, invalid data is exceptional (unexpected) conditions indicating a programming error, whereas invalid values are expected user input errors which can be dealt with in the code.

I would like to explain a thing or two about catching and handling exceptions. Consider the following method:

void someMethod(String pValue) throws Exception {
  try {
    // Do things that might throw an Exception
    ...
  }
  catch (Exception e) {
    throw e;
  }
}

If you ever feel the need to write code looking like that, then you must realise you are doing something wrong. The rule is simple - never catch an exception you cannot handle. Why catch it just to re-throw it because you cannot handle the exception? In this case, the code could have been written better like this:

void someMethod(String pValue) throws Exception {
  // Do things that might throw an Exception
  ...
}

Most developers I have met would very eagerly write code as shown below.

void someMethod(String pValue) throws DAOException {
  try {
    // Do things that might throw an Exception
    ...
  }
  catch (FileNotFoundException e) {
    throw new DAOException("Error occurred in someMethod: "+e.getMessage());
  }
}

Can you see what is wrong with this? In this scenario, the developer tried to wrap the exception in a more generic exception, which is considered good programming practise. Imagine if you always just throw the lowest level exceptions straight through to the front-end - you would have to handle many different exceptions. It is generally better to group them by wrapping them. In some instances you cannot get away from this, as is the case in RMI invocations in J2EE containers.

The problem with that code is that the stack trace has been lost. By throwing a new exception the original is lost. Many times the only way to understand the root of the problem is to view the stack trace for the original exception, which is now lost. Furthermore, imagine if the DAOException is caught by a Session Bean, and wrapped again in a RemoteMethodException... Now you would only see the problem originating at the catch handler of the session bean!

The solution is very easy if you are using Java 1.4+, as Sun introduced the concept of exception chaining to Throwable. The aforementioned code can now be (better) written as follows, assuming DAOException extends Exception.

void someMethod(String pValue) throws DAOException {
  try {
    // Do things that might throw an Exception
    ...
  }
  catch (FileNotFoundException e) {
    throw new DAOException("Error occurred in someMethod: "+e.getMessage(),e);
  }
}

If you are using versions of Java prior to 1.4, you are out of luck. The only way to achieve this behaviour is to define it yourself. An example has been provided, and you may use this in your code royalty free. Always extend this class instead of Exception if you want to chain exceptions.

public class NestedException extends Exception {
  private Throwable mRootCause;

  public NestedException(String pMessage) {
    super(pMessage);
  }

  public NestedException(String pMessage, Throwable pRootCause) {
    super(pMessage);
    mRootCause = pRootCause;
  }

  public Throwable getRootCause() {
    return mRootCause;
  }

  public String getMessage() {
    return super.getMessage();
  }

  public void printStackTrace(PrintStream pPS) {
    if (mRootCause == null)
      super.printStackTrace(pPS);
    else {
      pPS.println(this);
      mRootCause.printStackTrace(pPS);
    }
  }

  public void printStackTrace(PrintWriter pPS) {
    if (mRootCause == null)
      super.printStackTrace(pPS);
    else {
      pPS.println(this);
      mRootCause.printStackTrace(pPS);
    }
  }

  public void printStackTrace() {
    if (mRootCause == null)
      super.printStackTrace();
    else {
      System.out.println(this);
      mRootCause.printStackTrace();
    }
  }
}

This will only do half the work. The rest you need to do when displaying the exception, and the following code should help retrieving all the chained exceptions.

// Assume vE is the exception that needs to be displayed

ByteArrayOutputStream vB = new ByteArrayOutputStream();
PrintStream vPS = new PrintStream(vB);
vE.printStackTrace(vPS);
if (NestedException.class.isAssignableFrom(vE.getClass())) {
  Throwable vRootCause = ((ServletException)vE).getRootCause();
  while (vRootCause != null) {
    vPS.println("Root Cause: "+vRootCause.getMessage());
    vRootCause.printStackTrace(vPS);
    if (NestedException.class.isAssignableFrom(vRootCause.getClass()))
      vRootCause = ((NestedException)vRootCause).getRootCause();
    else
      vRootCause = null;
  }
}

Using this in your code will ascertain that the whole stack trace be preserved, and displayed in full when needed.

I hope this article helped illustrate the proper way of using exception handling statements in creating more robust, error free programs. Just always remember to step through the code you just wrote manually by hand, and try to reason what would happen on exceptional and normal conditions. If the flow of the code makes sense, then it is a good indication that you are on the right track. However, if you see obvious problems, then you saved yourself hours of difficult debugging sessions. A proper self code review normally helps find 90% of all bugs in a system. Unfortunately many developers are too lazy to review their own code as they write it, and suffer the consequences of hours of endless debugging sessions.