Designing Exceptions

What should be considered when writing new exception classes?

Use existing Exceptions

Prior to writing a new exception try to reuse existing ones. Code reuse is one of the primary goals of software development. But reuse must be based on semantics, not on the name. So be sure to check the documentation to the exception prior to using it.

Choose a meaningful Name

As for any class, method or variable, choose a meaningful and precise name for your exception class. It is often useful to remind the developer that an exception is a subclass of another exception by a naming convention, although this is not always possible.

Subclass Exceptions

If reusing an exception as is is not an option, consider to subclass an existing exceptions. This makes it easier for clients to catch this kind of exception. Grouping exceptions by sub-classing is always a good idea.

Design Exception Class Hierarchy

It is very helpful to have an exception class as the root for all exceptions of an application. Subclasses handle different aspects of exception cases of this application. But how deep should this hierarchy be? Michael Feathers states

"Use different classes only if there are times when you want to catch on exception and allow the other one to pass through."
Clean Code, p. 109

The exception codes of smartics-exceptions help to follow this rule. You simply have one exception class that distinguishes the particular exception event by the code number. But if you ever feel like checking the code in a catch clause you must write a new exception subclass.

Another reason to write a new exception class is, if the context of the exception event provides useful and specific information.

To have two reasons to create a class is somewhat odd. Therefore you might want to separate the type of an exception to be caught from the information that is provided by the message. Please refer to Separation of Exception Type and Information for details.

Know the Exception Categories

Basically there are three categories of exceptions.

  1. JVM exceptions
    are thrown by the Java Virtual Machine. They are by definition unrecoverable and unchecked. Try to shutdown the application gracefully and restart. There is nothing more to do.
  2. System exceptions
    are thrown because of problems the system encounters with itself. There is no user interaction involved. This is most often a resource or configuration problem. These exceptions are unchecked and should be reported immediately to the system administrator to be resolved.
  3. Application exceptions
    are thrown by the application or a library because the information provided by the user was not as expected. The business process could not execute successfully because some conditions have not been met. Application exceptions are runtime or checked exceptions to allow the control flow to try an alternate path.

Please refer to Consider to use Faults and Contingencies for information on how to design runtime and checked exceptions based on these concepts.

Know when to use checked or unchecked Exceptions

Create a checked exception by inheriting from Exception, if it cannot be prevented by proper use of the API and it describes a situation where clients usually can recover from. Only in this case the user of a method throwing the exception will benefit from catching it. If it's benefit is not obvious or if the situation is recoverable, favor unchecked exceptions.

Checked exceptions

  • force the client to handle certain error conditions.
  • document the possibility of error conditions.
  • greatly enhance the reliability.
  • over-used, make an API less pleasant to use.

Create an unchecked exception by inheriting from RuntimeException for unrecoverable error situations, where the client to a service cannot do anything to deal with the problem. This kind of exceptions are also often used to indicate precondition violations.

By convention new Error class should not be created. Derive from runtime exception instead.

Never inherit from Throwable directly.

Consider to use Faults and Contingencies

In his article Effective Java Exceptions Barry Ruzek advises to distinguishes between

  1. Faults
  2. Contingency

in an exception design.

Where faults basically are used to signal problems that usually cannot be handled by the client (such as database connection or IO problems), contingency exceptions model business problems (like insufficient funds or overdraft exceptions). Barry Ruzek summarizes

Condition Contingency Fault
Is considered to be A part of the design A nasty surprise
Is expected to happen Regularly but rarely Never
Who cares about it The upstream code that invokes the method The people who need to fix the problem
Examples Alternative return modes Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers
Best Mapping A checked exception An unchecked exception

Source: Mapping Java Exceptions, Barry Ruzek.

Make your faults class inherit from AbstractLocalizedRuntimeException and your contingency exception class from AbstractLocalizedException.

For more information about faults and contingency exceptions, please refer to Barry Ruzek's article.

Provide useful Information

Exceptions should provide information to tell the client of the service what went wrong and how to deal with the problem. This is important for checked exceptions as well as unchecked exceptions.

Provide specific Information

If not all instances of an exception provide the same information, make two subclasses. Do not let the client of you exception guess which properties are provided.

Do not be tempted by laziness and add a property to the existing exception class that is only meaningful in some cases.

Do not change Information after the Exception is thrown

All fields of an exception should be final so that information cannot be changed after the exception is thrown.

There is a Checkstyle rule to check for MutableExceptions.

Mind Serialization

Exceptions are serializable. Be sure that every property you add to your exception class is also serializable.

Please refer to Testing Serializability for a tip on writing tests on serializability.

Provide a meaningful Message

The message printed to the log file should be meaningful and to the point. While Joshua Bloch states

"Lengthy prose descriptions of the failure are generally superfluous; the information can be gleaned by reading the source code.

The detail message of an exception should not be confused with a user-level error message [...]. Unlike user-level error message, it is primarily for the benefit of programmers or field service personnel [...]."
Effective Java, p. 254

We deem it important that the context of the problem is obvious without knowing the source code.

Therefore we allow to provide information at a number of levels:

  1. title - a short categorizing information that can be used as dialog titles.
  2. summary - a short summary of what has happened.
  3. details - more detailed information of what has happened for an audience that is investigating the problem.
  4. implications - that what has happened has this implication on the business task that encountered the problem.
  5. todo - what can (or has to be) done to continue working?
  6. url - a reference to further static information on the problem encountered

For further information on textual presentation of failures you may have a look at chapter 4 of GUI Bloopers, especially Blooper 28: Vague error messages.

Most exceptions - with an exception of certain presentation tier exceptions - are targeted at the service personnel and software developers. Therefore it is important to given them as much information possible to spot and fix the problem timely.

For smart exceptions the keys mentioned above are defined in MessageType. A code properties file may contain the following keys:

1000.title=...
1000=...
1000.details=...
1000.implications=...
1000.todo=...
1000.url=http://www.examples.com/information-system/system-messages/message-1