Throwing Exceptions

What are best practices for throwing exceptions?

Use Exceptions only for exceptional Conditions

A programmer must not be forced by an API to catch an exception to get information about the state of the service s/he is using. S/he should never use exceptions to do clever tricks.

Joshua Bloch states it like this:

[E]xceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary flow control. [...] A well-designed API must not force its clients to use exceptions for ordinary flow control. Effective Java, p. 242

For example the Iterator provides a method hasNext() to check if there are further elements to iterate over. There is no need to check for the NoSuchElementException when calling next().

But exceptionali> might not be as exceptional as one might think. In his article Effective Java Exceptions Barry Ruzek advises to use exceptions for business problems that may be regularly encountered, but are not part of the main course of events. For more information on this topic please refer to Consider to use Faults and Contingencies or refer to Barry Ruzek's article.

Know Common Exceptions

There are some basic exceptions that are often used, especially for precondition checks. This table is based on Effective Java, p. 249

Exception Occasion to Use
IllegalArgumentException This is the classical precondition check to validate non-null input arguments.
NullPointerException Also a precondition check that an argument is not null. It is also used if a method returns a null value which is not expected.
IllegalStateException The state of the object is invalid prior to the invocation of a method. This problem may either be temporary or final without a cure.
IndexOutOfBoundsException An index parameter is out of the acceptable range.

Since argument checks are often used there are libraries to specify those checks very concisely. Please refer to our blog post Fail-Fast with Argument Checks for details on argument checks.

Failure Atomicity

Throwing an exception should keep the object in the state it was prior to the method call. This is not an issue for errors, but for checked and unchecked exceptions only.

This is archived by following these rules:

  • Check the input arguments and fail on any violation of preconditions.
  • Make no change to the internal state until you are sure the operation has run successfully. Then change the state with the calculated values.
  • If you cannot run the method without changing the state consider
    1. running the operation on a clone.
    2. providing recover code that corrects the changes already made on failure.

Never fail in a Finally Block

Make sure to not throw an exception in the finally block. If you throw an exception in the finally block, the caught exception gets lost.

Never fail during the Construction of an Exception Instance

If you run into an exception when creating an exception instance, the caught exception gets lost. This is a very serious problem.

Where you usually make sure to throw IllegalArgumentExceptions and IllegalStateExceptions on checking invalid preconditions, this is not an option for constructors of exceptions. Exceptions have to deal with null values accordingly during the lifetime of the exception instance (i.e. within every method).

Preserve Stack Trace

The stack trace provides valuable information about where a problem is located. When re-throwing an exception make sure to preserve this stack trace by exception changing unless you have proper reason to use exception translation.

If the exception you want to throw does not allow to add an exception as cause, use tip Exception with no Cause.

Note that static analysis tools, like PMD, allow you to spot locations, where the stack trace may get lost.

Do not delegate Throwing the Exception

The location where the exception is thrown should be the location the exceptional condition has been encountered. If the act of throwing the exception is delegated to a helper class, the original location is obscured.

if(exceptionalCondition) {
  final ExceptionHelper helper = new ExceptionHelper();
  helper.handleException();
}

This usage has also the problem that the compiler cannot predict, whether or not an exception is actually thrown (even if that would+ always be the case). Therefore, if you want to delegate some exception handling to a helper, only delegate the creation of the exception instance:

if(exceptionalCondition) {
  final ExceptionHelper helper = new ExceptionHelper();
  throw helper.createException();
}