Error Handling - Ignore, Hide or Run Away
Disclaimer: Note that throughout this article I am use System.out and/or System.err to send error messages to the user. In a real application you should NOT use System.out or System.err unless you are working with a command line application and even then you may not want to use them. Instead you should use a logging utility. See my previous article on Java Logging Made Easy for information about how to replace your System.err and System.out with good logging.
Throughout my years of college, taking programming training courses and tutoring, I have found a never ending deluge of bad programming practices. Most of these bad practices stem from the fact that the instructor feels they don't have the time to teach everything so they teach what they need to. Unfortunately, every programming course I have taken or tutored for glosses over or simply ignores a very critical element: error handling.I once heard that error handling can make up as much as 50% of the code in an application. Personally I have found this estimate to be a bit high but I would not be surprised if at least 25% of my code is devoted to error checking and handling. I can definitely agree that about half of the user interface code is dedicated to error checking and handling.
For this article I am not going to go into error checking. Error checking is the code that makes sure required fields are filled in, valid data is typed in by the user and severe errors do not occur. Error checking is proactive programming, where the programmer anticipates what could go wrong and either prevents the actions from being performed or warns the user of their invalid actions. I see this taught, to a certain degree, in classes and the methods for doing error checking vary depending on the user interface. I will show some nice error checking code in later articles when I cover Swing, Struts or other GUI focused topics.
Error handling is a more reactive approach to dealing with errors in your system. In a perfect world, you never have to be reactive. All errors are checked and dealt with properly and the system runs smoothly.
We don't live in a perfect world. Moreover, your boss is probably never going to give you the time to make the perfect system.
So, even though we want to be proactive and never have to mess with error handling, it is a necessity. Good error handling helps us uncover bugs we never thought could occur, discover their source and fix them quickly.
What is an Exception
Exceptions are ways for the code to tell you, "Hey, there is something wrong here!" In the old C days, functions would return error codes to let you know if something is wrong. If the error code indicated everything ran smoothly, you could continue without pause. Otherwise you had to compare the error code with a list of possible problems to see what really happened. There are a few problems with this approach.The first problem is that, since you can only return one thing from your function, you cannot return something useful when errors don't occur (which should be the majority of the time). If I called a function like
openFile
it would return an error code. But what I really wanted was the file handle so I could start reading the contents of the file. So the function had an additional argument where the file pointer could be stored. This is more of a hack or work around. The second problem is that developers often ignored the error. They call the functions and don't even grab the error code or ask if the function call even succeeded. It took self discipline to write code with proper error handling.
The third problem was the fact that you had to have good documentation of all the possible things that could go wrong. Then, in your program, you would have a case statement of if / else blocks to test each possibility. If you forgot one of the error codes, you didn't find out until later when everything went awry.
However, with exceptions you list out the things that can go wrong in the method signature. For example:
public User logInUser(String userName, String password) throws InvalidUserException, InvalidPasswordException, LoginNotAvailableException;Immediately with this syntax you can see the three things that can go wrong when attempting to log in AND you the programmer are forced to deal with it. The method also returns the user object which is what you were wanting in the first place.
In Java, you have two options when dealing with exceptions: you can handle it or you can pass it on. Here are two examples:
// Handling an exception public void logIn() { User user = null; try { user = logInUser("admin", "password"); System.out.println("Logged in as " + user.getFirstName()); } catch (InvalidUserException e) { System.err.println("Invalid User"); } catch (InvalidPasswordException e) { System.err.println("Invalid Password"); } catch (LoginNotAvailableException e) { System.err.println("Unable to log in"); } finally { System.out.println("Done"); } }
// Passing an exception on public void logIn() throws InvalidUserException, InvalidPasswordException,LoginNotAvailableException { User user = logInUser("admin", "password"); System.out.println("Logged in as " + user.getFirstName()); }
In the first you see that we have to catch each exception. We could have also just done a single catch (Exception e)
to catch them all. Note that every try
needs one catch
or one finally
. Each try
can have multiple catch
blocks, each catch for a different exception, and can only have one finally
.
Note also that if anything goes wrong in the try block, execution of the try ceases immediately and any code that remains unexecuted in the try is skipped. If the exception is caught, the catch block is run. The finally is run regardless of whether an exception is thrown or not. As you will see in the examples later on, the finally block is an ideal place to put code that closes IO streams and database connections. Consider it your clean up spot.
In the second logIn()
we just declare the exceptions in the throws clause and make anyone who uses the logIn()
method deal with the problem.
In Java, there are several classes you should be familiar with when it comes to error handling. These classes are Throwable, Error, Exception and RuntimeException. The class diagram for this is shown below.
At the top is the Throwable. You can only throw objects that are subclasses of Throwable. The throwable provides two things that are highly useful to the programmer: a message and the stack trace. The message tells us, if the code throwing the exception was well written, what the problem is. The stack trace lists out the stack of code that was running when the exception was thrown with the names of the classes, methods and line numbers. It is with these two items that I fix about 99% of the errors in my applications that cause exceptions to be thrown.
The two main subclasses of Throwable are Error and Exception. Errors are abnormal conditions and indicate serious problems. The Java Docs state that Errors do not need to be declared in throws clauses or be caught. In fact, the Java Docs go as far as to state that you SHOULD NOT attempt to catch errors. Typically errors are fatal to the application and are most often caused by someone killing a Java process.You will also see errors if you use JNI and your C++ code has errors.
The Exception class is what we will focus on here. It is what you, the programmer will see and have to deal with. The exception has many subclasses but the one you should be most familiar with is the RuntimeException. Anything that extends Exception but does NOT extend RuntimeException must be either caught or declared in the throws clause of the method where the exception appears. Anything that extends RuntimeException does not need to be caught or declared in the throws clause (this is a good and a bad thing as you will see below).
The Typical (and Bad) Error Handling
Most new and, unfortunately, some experienced developers go about handling errors the wrong way. Here are the three ways I have seen errors dealt with and an explanation of why they will cause you grief. I call them the Ignore, Hide or Run Away approaches.Ignore (aka Bite You In The Ass)
In Java the RuntimeException is one that can be ignored. It includes exceptions like NullPointerException, ArrayOutOfBoundsException and IllegalArgumentException. You can find many RuntimeExceptions in the java.lang package.RuntimeExceptions can be thrown in a method without the method declaring it in the throws clause. For example:
// This is an example of how NOT to use exceptions public void checkIntString(String args) { for (int i = 0; i < args.length(); ++i) { if (! Character.isDigit(args.charAt(i))) { throw new IllegalArgumentException("Character at position " + i + " is not a digit"); } } }The reason for having RuntimeExceptions is that, without them, you would need try/catch or throws almost everywhere. Every time you use the . operator a NullPointerException can be thrown. Every time you access an element of an array an ArrayOutOfBoundsException can be thrown. The RuntimeException makes sure you don't have to put try / catch blocks everywhere. You can say that the try / catch is the exception and not the rule... ok, enough of the puns.
The problem with RuntimeExceptions is that anyone who calls our checkData(args) method above doesn't know they need to deal with possible errors. It is convenient for the programmer because they don't have to add error handling code but problems will begin to pop up once the system is put into use. At that time, the users have to deal with the errors. In many cases the application begins to act erratic or even crash. Convenient for the programmer, frustrating for the end user.
Instead, you should use non-RuntimeExceptions whenever possible. This forces the programmer to address potential errors during the coding process and does not drop the bomb into the user's lap.
// This is an example of how to use exceptions import java.text.ParseException; ... public void checkIntString(String args) throws ParseException { for (int i = 0; i < args.length(); ++i) { if (! Character.isDigit(args.charAt(i))) { throw new ParseException("Character at position " + i + " is not a digit", i); } } }When .NET first came out I purchased a book about C#. I was appalled to find that every exception in .NET is a RuntimeException (or at least the C# equivalent). What this means to me is that we want to make life easier for the programmer and force the testers, or worse the users, to find the errors. If you use non-RuntimeExceptions, you will be forced to deal with error handling just to get the program to compile. The compiler will tell you where to put the error handling, you won't have to go searching all over for potential problems. Wait a minute... this is being proactive... good for us Java programmers!
I didn't finish exploring this .NET "feature" so there may be more to the story so any of you .NET developers out there, let me know if I missed something.
Hide
The most common way I have seen inexperienced developers handle exceptions is via the hide approach. This is a very simple and potentially disastrous way to deal with exceptions.All non-RuntimeExceptions must be either declared in the throws of the method signature (as seen in the second
checkIntString
example above) or caught. To hide an exception you just wrap the offending code with a try / catch and leave the catch empty. For example:
// This is an example of how NOT to handle exceptions import java.sql.Connection; import java.sql.DriverManager; ... public Connection getConnection(String url, String user, String password) { Connection conn = null; try { conn = DriverManager.getConnection(url, user, password); } catch (Exception e) { } return conn; }I ran into this one once in a production environment. A developer had written an application in Java and was no longer with the company (after reviewing their code I felt this was a good thing). Many months later, the database was moved to anew server and suddenly the application stopped working. It was giving an odd NullPointerException in a completely different part of the application and none of the people maintaining the application could figure out why. After several hours of agony they brought me in. They got me a copy of the code and I spent an hour or two digging around and trying to understand what was written. I finally found the problem was that the IP address of the database had changed and the getConnection method was failing. Of course the exception was ignored as if everything was OK when in fact everything was NOT OK. If this exception had not been ignored, the problem would have been immediately identifiable and fixed in minutes. Once everyone's time was added up, the error cost about $1500 in labor to fix and who knows how much money to rewrite the code so it handled errors properly. If the developer had coded it properly at the beginning it would have taken him minutes to write something that would have saved the company thousands of dollars.
Run Away
In the secondcheckIntString
example above I showed you how you can declare the exception in the throws part of the method signature. I consider this good practice when writing utility classes or API's that will be used in many places or by many developers. But you don't want to get carried away with it. For example:
// This is an example of how NOT to use exceptions public abstract class Task { public abstract void setArgs(String[] args) throws Exception; public abstract void checkUsage() throws Exception; public abstract void setUp() throws Exception; public abstract void doTask() throws Exception; public abstract void shutDown() throws Exception; public static void main(String[] args) throws Exception { Task task = (Task) Class.forName(args[0]).newInstance(); task.setArgs(args); task.checkUsage(); task.setUp(); task.doTask(); task.shutDown(); } }This is the opposite extreme from the hide method. Everything just throws Exception. I have seen developers put
throws Exception
in every method just like this, even if there is no need for the exception. It certainly makes it easier to code but notice the problem - no one is handling the exception. It just bubbles on up to the top and crashes the program. You should throw meaningful exceptions like this:
public class TaskException extends Exception { public TaskException(String message) { super(message); } public TaskException(String message, Throwable cause) { super(message, cause); } } public abstract class Task { public abstract void setArgs(String[] args); public abstract boolean isUsageCorrect(); public abstract String getUsageMessage(); public abstract void setUp() throws TaskException; public abstract void doTask() throws TaskException; public abstract void shutDown() throws TaskException; public static void main(String[] args) { Task task = null; try { task = (Task) Class.forName(args[0]).newInstance(); } catch (Exception e) { System.err.println(e.getClass().getName() + " creating task"); e.printStackTrace(); System.exit(-1); } task.setArgs(args); if (! task.isUsageCorrect()) { System.err.println(task.getUsageMessage()); System.exit(-2); } else { try { task.setUp(); } catch (TaskException e) { System.err.println("Error setting up task"); e.printStackTrace(); System.exit(-3); } try { task.doTask(); } catch (TaskException e) { System.err.println("Error running task"); e.printStackTrace(); System.exit(-4); } try { task.shutDown(); } catch (TaskException e) { System.err.println("Error shutting task down"); e.printStackTrace(); System.exit(-5); } } System.exit(0); } }Yes it is a lot more code but when it is run by a user, they will get a meaningful error message if there is a problem. Note that the main method does not throw an exception. I recommend that your main method should never throw an exception, you should always handle the error and then exit if necessary.
The Right Way To Handle Errors
Now that we know how NOT to handle exceptions, what are some rules on the proper ways to handle them. Here's how we really should do it...try/catch or throws?
The first thing you need to decide when dealing with an exception is whether to use a try / catch or declare it in the throws clause. The answer to the question is going to be rather subjective but I have some guidelines for helping you decide which approach to use.If you are working with an API to be used by other developers, you should almost definitely declare the exception in the throws clause. You may want to catch any exception and wrap it with your own custom exception (see below for how this works) and this custom exception will then appear in the throws clause. Regardless, throwing an exception is a great way to communicate to other developers that they need to be aware that problems can occur and they need to be ready to deal with them and inform the user.
As with an API, if it is not clear what will need to be done with the exception when it arises, you will probably want to declare it in the throws clause. However, make sure that somewhere, eventually, the exception will be handled.
If the exception pops up at a place where you know how you want to handle it, that is the perfect place for a try / catch. Catch the exception, handle it appropriately and move on.
For example, let's say you have a web site that consists of JSP's, Servlets, Business Logic objects and DAO's. The JSP's render HTML. The user then clicks on links or submits forms to the Servlets. The Servlets tell the Business Logic what to do, the Business Logic tells the DAO's to do inserts, updates or deletes and the DAO runs the SQL against the database. This is common in 3-tiered web applications. If there is an error on the database, perhaps you are trying to add a user but that user name is already taken, the DAO will receive an SQLException. What I do in this situation is have the DAO pass the exception on up to the Business Logic. The Business Logic then wraps the exception with its own BusinessLogicException (or maybe even a UserExistsException) and throws that to the Servlet. The Servlet then catches the exception, prepares a nice error message for the user and sends the message to the JSP which then displays it to the user.
In this example, the DAO had no idea what to do with the exception. It did not know if the caller was a web site or a desktop application. So, it dumbly passed the exception on up the chain.
The Business Logic also didn't know what the caller was. Keep in mind that, in order to maximize reuse, we want to design the Business Logic to be usable by a desktop application, by another web site or maybe even a web service. The web site doesn't need to know we are using a database. We could be using Active Directory, a SOAP service or a simple file. So the Business Logic needs to transform the SQLException into some generic business error - so we catch the SQLException, wrap it in a BusinessLogicException and throw the BusinessLogicException.
Once we get to the Servlet level, we now know what we want to do with the exception. We extract the message from the exception, log the error and send the user back to the page they were on and display the error message.
In general I attempt to throw the exception (or the wrapped exception) up to the user interface level. Ultimately the error needs to be presented to the user so, in most cases that is the most logical place to catch the exception. On a desktop application you can then display an error dialog to the user.
You should be specific when declaring an exception in your throws clause. Don't
throw Exception
unless you have to. Some good examples of where you may consider using throw Exception
are in interface methods where you really don't know what may go wrong in specific implementations - even then you may want to create your own special exceptions). What to Put Where
The throws clause is pretty straight forward. You simply declare each exception thrown within the method declaration.With the try / catch / finally there are many choices and quite often inexperienced developers choose poorly.
Before the try block you should declare any variables you may need in your catch or finally blocks. Initialize their values to null in the declarations. If you are doing file IO, you should declare your IO streams. If you are connecting to a database, you should declare your connection, statements and result sets.
Next, put the rest of the code that is used if there is no exception thrown into the try block. If any of the code at the end of your try block will need to run regardless of whether an exception is thrown or not, you should move that code to the finally.
Last you should add your catch blocks and add the code that handles each exception caught.
Example: here is a utility class that checks the contents of a text file to see if a specific string is present on any one line in the file. The method will notify the user about any errors but will just return false if it has problems reading the file. In many situations you would declare the IOException in the throws clause and remove the catch block.
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; ... public boolean fileContains(File file, String str) { // Declare the variable needed in the try and finally BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); String line = null; while ((line = reader.readLine()) != null) { if (line.indexOf(str) >= 0) { return true; } } return false; } catch (IOException e) { // Let the user know there was a problem. System.out.println("Error reading file:"); e.printStackTrace(); return false; } finally { // No matter whether the try succeeds or fails, // we should close the file - for this reason we // close the BufferedReader in the finally. // Note that even though the try and catch blocks // have a return statement, the finally will run // BEFORE the value is actually returned. try { if (reader != null) { reader.close(); } } catch (Exception e) { System.err.println("Error closing file read"); } } }If you are putting a throws into your method declaration, you should still consider using a try / finally. For example:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; ... private String dbUrl; private String dbUser; private String dbPassword; // Attempt to save the user to the database. public void saveUser(String userName, String password, String firstName, String lastName) throws SQLException { Connection conn = null; PreparedStatement stmt = null; try { conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword); stmt = conn.prepareStatement( "INSERT INTO app_user (" + " user_name, " + " pass, " + " first_name, " + " last_name" + ") VALUES (" + " ?, ?, ?, ? " + ") " ); int index = 1; stmt.setString(index++, userName); stmt.setString(index++, password); stmt.setString(index++, firstName); stmt.setString(index++, lastName); stmt.execute(); } finally { // Close the statement and connection. Note that // we close the statement separately from the // connection so that, if there is a problem closing // the statement, it doesn't stop us from closing // the connection. try { stmt.close(); } catch (Exception e) { System.err.println("Error closing statement"); } try { conn.close(); } catch (Exception e) { System.err.println("Error closing connection"); } } }Also make sure you put your catch blocks to work. At the very minimum, most if not all of your catch blocks should have some kind of logging. If the exception caught is rather benign, you can log at a debug or info level (see my Java Logging Made Easy article for details on logging). If it is important that we take notice of and deal with an exception we should log at the error or at warning level.
Example: Here is a utility that attempts to delete a temp file. Since, if the delete fails, it really doesn't create an immediate problem, we just want to log it and continue on without stopping the normal operations of the program. Someone will check the logs later and find that the temp file was not deleted properly and fix the problem then.
import java.io.File; ... public void deleteTempFile(File file) { try { file.delete(); } catch (Exception e) { System.err.println( "Unable to delete temp file " + file.getAbsolutePath() + ": " + e.getMessage() ); } }A user interface will want to collect errors and display them to the user. Sometimes one error is fatal enough to stop execution and display the error right away but at other times you may want a list of errors to show the user.
import java.text.ParseException; import java.util.ArrayList; import java.util.List; ... private List errors = new ArrayList(); private String formatPhoneNumber(String phone) throws ParseException { StringBuffer numbers = new StringBuffer(); for (int i = 0; i < phone.length(); ++i) { char c = phone.charAt(i); if (Character.isDigit(c)) { numbers.append(c); } } if (numbers.length() != 10) { throw new ParseException("Phone number has only " + numbers.length() + " digits", phone.length() - 1); } numbers.insert(0, '('); numbers.insert(4, ')'); numbers.insert(8, '-'); return numbers.toString(); } public String[] formatPhoneNumbers(String[] phoneNumbers) { errors.clear(); List formattedNumbers = new ArrayList(); for (int i = 0; i < phoneNumbers.length; ++i) { try { formattedNumbers.add(formatPhoneNumber(phoneNumbers[i])); } catch (ParseException e) { errors.add(e.getMessage()); } } return (String[]) formattedNumbers.toArray(new String[formattedNumbers.size()]); } public String[] getErrors() { return (String[]) errors.toArray(new String[errors.size()]); }Some software has built in ways to accumulate and display errors to the users. For example, Struts allows the web application to build up a list of errors and even associate those errors to particular fields on the web page. This allows you to display error along side the field where the error is occurring making it easier for the user to discern what is wrong and where and quickly fix it.
Then there are times you need to wrap an exception. If we go back to the Task and TaskException in an earlier example, let's say we want to create a Task that starts at a given directory and determines the size of all the files in that directory and all the directories beneath it and prints the total size to the user. If there is an error, a TaskException will be thrown. Although the SecurityException is a RuntimeException and we could just let it go, we really should catch it and wrap it in a TaskException so the Task library will handle the problem properly.
import java.io.File; ... public class DiskUsage extends Task { private String[] args; private File dir; public void setArgs(String[] args) { this.args = args; } public boolean isUsageCorrect() { return (args != null) && (args.length == 2); } public String getUsageMessage() { return "Usage: java Task MyTask [directory]"; } public void setUp() throws TaskException { dir = new File(args[1]); } public void doTask() throws TaskException { long size = getSize(dir); System.out.println( "The directory " + dir.getAbsolutePath() + " and its subdirectories contain " + size + " bytes" ); } private long getSize(File directory) throws TaskException { long size = 0; try { File[] files = directory.listFiles(); for (int i = 0; i < files.length; ++i) { if (files[i].isDirectory()) { size += getSize(files[i]); } else { size += files[i].length(); } } } catch (SecurityException e) { // Note we cannot catch Exception as that will grab the // TaskException if thrown by our recursive call to getSize. // The Java Docs for java.io.File tells us that the methods // listFiles(), isDirectory() and length() can throw // SecurityException which is actually a RuntimeException throw new TaskException( "Error attempting to get size of directory " + directory.getAbsolutePath(), e ); // Note also that we put the SecurityException INSIDE of // the TaskException so when we print out the stack trace // we get the stack trace from both exceptions and can // see where the problem started. } return size; } public void shutDown() throws TaskException { // Nothing to do } }Why wrap exceptions? Why not rethrow the same exception or, as we could have done in the last example, just let the RuntimeException go?
First of all, when you are writing code that extends or implements code from another API, you should always follow their exception handling. If you do not, you can get unpredictable results. What would have happened if we had let SecurityExceptiongo without wrapping it with TaskException? After all, Task was expecting a TaskException, not a SecurityException. In our case, Task would NOT have handled the exception properly and the program would have unexpectedly terminated. You should try to throw exceptions that the caller expects.
You should also avoid rethrowing the exception you caught:
// Don't do this import java.io.FileInputStream; ... try { FileInputStream in = new FileInputStream("test.txt"); // More code here... } catch (Exception e) { System.err.println("Error creating input stream..."); e.printStackTrace(); throw e; }The better option is to wrap the exception in a new exception. This will provide more detailed information about what code actually ran and also allows you to change the exception type which can then be more specific to operation that you were attempting to perform.
import java.io.FileInputStream; ... public class MyException extends Exception { public MyException(String message, Throwable t) { super(message, t); } } ... try { FileInputStream in = new FileInputStream("test.txt"); // More code here... } catch (Exception e) { System.err.println("Error creating input stream..."); e.printStackTrace(); throw new MyException("Error creating input stream", e); }Now when the MyException is finally caught and a stack trace is produced, you will see not only the stack trace for MyException but you will also see the stack trace for
Exception e
stored inside it. I hope this has provided you with an arsenal of techniques to help you do your exception handling like an expert. With the proper exception handling, you will experience fewer support issues and have much more stable applications.