Exceptions

Goals

Concepts

Language

Javadoc

Library

Lesson

Things do not always go as planned. If a caller passes bad data to a method (e.g. the caller requests a method to divide by zero), or a method has problems accessing resources such as a file, it needs a way to report the problem back to the caller. Java provides a special facility for elegant error-handling, in line with most modern programming languages.

Return Codes

Historically there have been several approaches to error-reporting. One of the earliest was the use of special return values. If a method returns a value, there may be certain values that have special meaning.

We saw for example that the Point.calculateSlope(…) method could return Double.POSITIVE_INFINITY to indicate that the resulting slope would be vertical. But this is not an error condition; this is only a special condition. We expect vertical lines to exist just as horizontal lines exist; they merely need to be treated specially.

Point.calculateSlope(…) method.
…

/**
 * Calculates and returns the slope of a line between this point
 * and the given coordinates.
 * @param x2 The other x coordinate.
 * @param y2 The other y coordinate.
 * @return The slope of the resulting line, or {@link Double#POSITIVE_INFINITY}
 *    if the resulting line would be vertical.
 */
public double calculateSlope(final int x2, final int y2)
{
  final int deltaX = x2 - x;
  final int deltaY = y2 - y;
  if(deltaX == 0)
  {
    return Double.POSITIVE_INFINITY;
  }
  return (double)deltaY / (double)deltaX;
}

…

On the other hand there exist unexpected conditions that in a particular context should be understood as an error condition. Let's say that we want to create a method for a class called ComputerScreen that will display a happy face ☺ on the screen at some coordinates. The method might look like this:

ComputerScreen.showHappyFace(…) method.
…

/**
 * Shows a happy face on the screen at the given coordinates.
 * @param x The horizontal coordinate; must be zero or greater.
 * @param y The vertical coordinate; must be zero or greater.
 */
public void showHappyFace(final int x, final int y)
{
  …
}

…

But what if one of the coordinates is negative? The happy face will never appear (which will make the user have a sad face ☹). This is not what the method expected to happen; how can it notify the calling method that something is not right? Long ago computer programs would use special return codes that represented error conditions. For example, the above method might be adapted to return a boolean value indicating true if success or false for failure.

showHappyFace(…) method with old-fashioned error handling.
/**
 * Shows a happy face on the screen at the given coordinates.
 * @param x The horizontal coordinate; must be zero or greater.
 * @param y The vertical coordinate; must be zero or greater.
 * @return <code>true</code> if successful showing a happy face.
 */
public boolean showHappyFace(final int x, final int y)
{
  if(x < 0 || y < 0)
  {
    return false;  //old-fashioned error handling
  }
  …  //show the happy face
  return true;
}

There are many problems with “special value” error-handling, including:

Using the showHappyFace(…) method with old-fashioned error handling.
if(!showHappyFace(x, y))
{
  //TODO handle error
}
if(!printResults(results))
{
  //TODO handle error
}
if(!sendEmail(to, message))
{
  //TODO handle error
}

The last point above is especially important: the program should tend to its normal processing of data without being distracted with error conditions. There needs to be a way to relegate error handling to a separate part of the program, outside the normal program flow.

Exceptions

Most modern programs now have the idea of exceptions. When an error condition is encountered, the program can create and then throw an exception object. (Some programming language say raise an exception.) Error handling can then be relegated to a central location in another part of your program to catch an exception that might have been thrown.

Throwing Exceptions

Throwing an exception bypasses the normal return mechanism of a method. Processing of the method will immediately stop, and the call stack will unwind, as programmers say. That is, program execution will jump out of the current function to the previous, and then jump out of that method to the previous, and so on. This will continue until a special error-handling section of the code is reached.

One of the most common exceptions to throw is java.lang.IllegalArgumentException, which indicates that an erroneous or inappropriate argument was passed as a parameter.

showHappyFace(…) method using exceptions.
/**
 * Shows a happy face on the screen at the given coordinates.
 * @param x The horizontal coordinate; must be zero or greater.
 * @param y The vertical coordinate; must be zero or greater.
 * @throws IllegalArgumentException if one of the arguments is negative.
 */
public void showHappyFace(final int x, final int y)
{
  if(x < 0 || y < 0)
  {
    throw new IllegalArgumentException("Negative coordinates not supported.");
  }
  …  //show the happy face
}

Exception Handling

Once you throw an exception, something has to catch the exception at some point. Catching and processing an exception is called handling the exception, and Java uses a special try … catch(…) statement pair for doing this.

Here is how try … catch(…) works:

  1. Somewhere up the call stack (that is, in one of the callers), put all your statements that can throw some related exception(s) inside a try{…} block.
  2. Immediately beneath the try{…} block, put a catch(…){…} block, indicating inside the parentheses the exceptions you want to catch.
  3. (optional) Provide a finally{…} block for any code you want to always execute, whether or not an exception was thrown.
Using the showHappyFace(…) method exception handling.
try
{
  showHappyFace(x, y);
  printResults(results);
  sendEmail(to, message);
}
catch(final IllegalArgumentException illegalArgumentException)
{
  System.out.println("One of the methods didn't like the input.");
}
finally
{
  System.out.println("done"); //will always print, whether or not there was an exception
}

Checked Exceptions

Exception classes such as java.lang.IllegalArgumentException, which derive from java.lang.RuntimeException, are called unchecked exceptions.The special low-level error classes that derive from java.lang.Error, such as java.lang.AssertionError, are also unchecked exceptions. Any method can throw instances of unchecked exceptions, even if the method or its documentation never indicates that it throws exceptions. Similarly there is no requirement that you catch unchecked exceptions in your program, or otherwise deal with them.

All exceptions that do not derive from java.lang.RuntimeException are called checked exceptions. These come with two requirements:

One common checked exception is java.io.IOException, which indicates some sort of input/output error. Here is how the sendEmail() method above might be implemented, as sending email requires communication over TCP/IP.

Throwing a checked exception from the sendMail(…) method.
import java.io.IOException;

…

/**
 * Sends an email message.
 * @param to The recipient email address.
 * @param message The message to send
 * @throws IllegalArgumentException if the "to" argument is not a valid email address.
 * @throws IOException If the Internet connection goes down.
 */
public void sendEmail(final String to, final String message) throws IllegalArgumentException, IOException
{
  if(!Email.isAddressValid(to)) //call other method to check the email address
  {
    throw new IllegalArgumentException("Not a valid email address.");
  }
  …  //try to send the email
  if(…) //if the Internet connection goes down
  {
    throw new IOException("Lost connection.")
  }
}

The calling method may catch the IOException as well:

Catching multiple types of exceptions.
public void giveUserFeedback()
{
  try
  {
    showHappyFace(x, y);
    sendEmail(to, message);
  }
  catch(final IllegalArgumentException illegalArgumentException)
  {
    System.out.println("One of the methods didn't like the input.");
  }
  catch(final IOException ioException)
  {
    System.out.println("The Internet connection must have went down; please try again.");
  }
}

A calling method may instead decide not to catch a checked exception type, and instead simply declare that it too can throw that exception type, allowing the exception to “bubble up” the call stack if it is ever thrown.

Allowing exceptions to “bubble up”.
public void giveUserFeedback() throws IOException
{
  try
  {
    showHappyFace(x, y);
    sendEmail(to, message);  //any IOException thrown here will bubble up
  }
  catch(final IllegalArgumentException illegalArgumentException)
  {
    System.out.println("One of the methods didn't like the input.");
  }
}

Rethrowing Exceptions

Those two approaches—catching an exception or declaring the exception using throws—are not mutually exclusive. Part of the code could be guarded by a try … catch(…) statement, while other portions of the method could be unguarded and allow the exception to continue up the call stack. Even if an exception is caught, it can usually be rethrown simply by using the throw keyword again.

Rethrowing an exception.
public void giveUserFeedback() throws IOExeption
{
  try
  {
    showHappyFace(x, y);
    sendEmail(to, message);
  }
  catch(final IOException ioException)
  {
    //TODO log the error
    throw ioException;  //rethrow the error
  }
}

“Check” Methods

If you find yourself using the same error-checking code over and over, you may consider creating a separate method to do the check and throw the exception if needed. The name of such a method usually starts with “check…”, and acts as a simple wrapper around the error checking logic. If everything is OK, the check method does nothing. If there is a problem, the check method will throw the appropriate exception.

A “check” method for validating an email address.
public class Email
{

  public static boolean isAddressValid(final String address)
  {
    //TODO do some checks and return true or false
  }

  public static void checkArgumentAddressValid(final String address) throws IllegalArgumentException
  {
    if(!isAddressValid(address))
    {
      throw new IllegalArgumentException("Invalid email address.");
    }
  }

  /**
   * Sends an email message.
   * @param to The recipient email address.
   * @param message The message to send
   * @throws IllegalArgumentException if the "to" argument is not a valid email address.
   * @throws IOException If the Internet connection goes down.
   */
  public static void sendEmail(final String to, final String message) throws IllegalArgumentException, IOException
  {
    checkArgumentAddressValid(to); //throws an exception if invalid; otherwise does nothing
    …  //try to send the email
    if(…) //if the Internet connection goes down
    {
      throw new IOException("Lost connection.")
    }
  }
}

Review

Exceptions are conceptually revolutionary! They represent a huge departure from the old error-handling approach of checking the return value of each method. If you don't understand how exceptions represent a different paradigm than method return values, you should review this lesson.

Common Standard Exceptions

java.lang.NullPointerException
A null argument was passed when a parameter doesn't allow it. Java throws this exception automatically when you try to access the member of a null reference.
java.lang.IllegalArgumentException
An argument passed is inappropriate for the parameter. Technically an inappropriate null argument is also an “illegal argument” as well, but for null references Java convention is to use a NullPointerException.
java.lang.IndexOutOfBoundsException
The index of an array or list it outside the range. This could technically be considered an “illegal argument” as well, but is conventionally more appropriate for invalid indexes.
java.lang.IllegalStateException
The state of some values is unexpected or inappropriate to complete the operation requested in a method. In cases where both an argument is inappropriate because of an invalid object state, only throw IllegalArgumentException if there is exists some argument that could have worked with the current state; otherwise, if no argument would have worked, throw an IllegalStateException.
java.lang.UnsupportedOperationException
An object does not support a method, such as a read-only object that does not support modification methods. This exception is useful to use temporarily if you add a necessary method but have not implemented it.

Gotchas

In the Real World

Exception Messages

Real-world applications normally have to contend with several types of messages for different purposes. The message placed in an exception when it is created is meant more for the developer, not the user. The exception usually contains a stack trace, which is of little use to a normal user. The exception message might even contain confidential information that should not be given to the end user.

This is why the types of exceptions used are so important. Normally a program would provide some friendlier message to the user, perhaps even in the user's own language if different from the application default. The Nevertheless the exception message might be useful to place in a log, to help a developer or other technical support representative track down the problem.

Thus when you catch an exception it might be useful to do several things.

  1. Log the exception, its message, and its stack trace. Logging is explained in a future lesson.
  2. Present a friendly message to the user. Providing custom messages to the user in the appropriate language is discussed in a future lesson on internationalization.

Think About It

Self Evaluation

Task

Modify the Book.getYearsInPrint() method to make sure that the answer is valid. If it yields a negative number, that means the book's publishing date was set to some date past the CURRENT_YEAR, so throw an exception.

In the Booker method that prints all books, catch the exception representing an invalid publishing date for a single book, and inform the user that some unusual state or invalid data was encountered. Then continue printing the rest of the books.

See Also

References

Acknowledgments