Exception handling in a project is usually done in bits and pieces, like a fisherman trying to catch fish with holes in the net. As a result, users are occasionally presented with Java stack traces on the screen which reduce the impression of quality. Where exceptions are caught, the handling is often inconsistent, sometimes logging them, sometimes ignoring them, and sometimes showing more than one type of error screen which leads to an impression of low quality.
As a way of addressing this problem, applications should create their own base exception and subclass for each significant module in the application. For example, if a project is called “Timesheet”, a TimesheetException base class would be created. For the Timesheet Reports module, developers would create a ReportException base class. And so on.
An example hierarchy of Exceptions for the Timesheet project has the following family tree:
A well planned hierarchy makes exception handling easy. For example, normally a Controller call to the AccountingService should only need to catch AccountingServiceException types:
try
{
accountingService.save(user,timesheet);
}
catch (AccountingServiceException e)
{
errors.add(“A problem occurred during the save: ” + e.getMessage());
return VIEW_EDIT;
}
If more detail is required on what kind of AccountingServiceException it is, just catch the more specific exception first in the Controller:
try
{
accountingService.save(user,timesheet);
}
catch (EmployeeNotFoundException e)
{
throw new DataConsistencyError(“Fatal: not in accounting system”);
}
catch (AccountingServiceException e)
{
errors.add(“A problem occurred during the save: ” + e.getMessage());
return VIEW_EDIT;
}
You might ask, what about other exceptions that are not children of AccountingServiceException, like ConcurrentModificationException or IOException types? Normally these are wrapped in an AccountingServiceException, so if you really need this information, check the e.getCause() of the exception.
Trap: Don't allow a service to throw unrelated exceptions (e.g. NullPointerException) from a method call. Only throw exceptions that are defined within the service package or sub packages thereof.
To further clarify the structure, the internals of the AccountingService.save function might look like this:
public void save(TimesheetUser user, Timesheet timesheet)
throws AccountingServiceException
{
try
{
:
}
catch (AccountingServiceException e)
{
// It's already a type we like, rethrow it.
throw e;
}
catch (Exception e)
{
// Catch any exceptions we missed
throw new AccountingServiceException(e);
}
}
Notice that the save function try/catch block doesn't catch Throwable. There is a good reason for this – we want Error types to be able to bust out of the service completely, as described in the [exception.error] section. Error types are used to to handle serious problems in an application, so it's OK to throw Error in certain circumstances.