Exceptional Java
by Alan Griffiths

It must be approaching twenty years since I visited the
computer science department of the local university and on one
of the notice board spotted a chart for the next decade of
development in the industry. The feature of this that I
remember was that every odd year predicted "the end of Fortran"
and every even year "the end of Cobol". As the author (and I)
expected, these languages are still thriving far beyond the end
of that chart.

While to work in the software industry is to be exposed to
constant change there is much that is constant in spite of that
change. While we are regularly exposed to new tools
(technologies, languages, techniques, etc) our existing
knowledge and tools remain stubbornly useful. This is
especially true when we distinguish the fundamental ideas from
work-arounds for a specific tool.

For some years I've been very happy with the use of
exceptions in C++ programs
[1]
. Recently I accepted a position at a company working primarily
in Java, and consequently had to address the problems being
encountered by developers using this language.

Received wisdom

Before I go on to describe the problems encountered by my
new colleagues, let me revisit the "received wisdom" of the
Java development community. This is represented clearly by the
following quote from "Thinking in Java"
[2]
(similar views are expressed by other sources):

Keep in mind that you can only ignore
RuntimeExceptions
in your coding, since all other handling is carefully
enforced by the compiler. The reasoning is that a
RuntimeException
represents a programming error:

An error you cannot catch (receiving a null reference
handed to your method by a client programmer, for
example).

An error that you, as a programmer, should have
checked for in your code (such as
ArrayIndexOutOfBoundsException
where you should have paid attention to the size of the
array).

It is sensible to use
RuntimeExceptions
to report programming errors - the availability of a call-stack
aids reporting them meaningfully. The fact that they can be
handled far up the call stack makes implementing an
application-wide policy for handling them easier than trying to
do so at every point an error is detected. (Examples of
policies from different types of application domain are: to
abort the current operation, to restart the subsystem, or to
terminate the process.)

However, the "received wisdom" very clearly directs a
developer towards using ordinary, checked exceptions (i.e.
those not derived from
RuntimeException
) in the design of an application. The use of
RuntimeExceptions
is discouraged: "programming errors" do occur; but - beyond
having a policy for dealing with them when detected - we should
not be creating a design to cater for them! (Trying to cater
for bugs only leads to hard to test code that is, itself, a
breeding ground for bugs.)

The difference between theory and practice

The developers that I joined had attempted to apply this
guidance and run into a number of problems. However, because of
pressure to "just write the code" no attempt had been made to
formulate a workable design policy. Letting developers struggle
on independently can waste a lot of time over the course of a
project - so I called a meeting of the programmers on the team
I was working with to discuss the problems they were having
with exceptions.

We'll be examining more of the problems they described in
the rest of this article - this section deals only with those
that bear directly on the above theory (that checked exceptions
should be used for everything that isn't a programming error).
Before proceeding, I want to make it clear that this
development group isn't the only one to experience these
problems. They are in good company - as I confirmed at the ACCU
Spring conference last year. It seems that this theory doesn't
work in practice.

The relevant problems described fall into three
categories:

Breaking encapsulation

Consider the example of a factory method that is
responsible for creating objects. From the point of view of
the client code there is no obvious reason why it should
fail - so the interface doesn't have a throws clause. From
the point of view of an implementation that retrieves
objects from a database it is necessary to handle
SQLExceptions
. For the sake of this discussion let us assume that these
reflect something catastrophic - like connectivity to the
database being lost.

The
SQLExceptions
we are considering are not the result of programming
errors. Accordingly, we are exhorted not to propagate them
as unchecked exceptions. On the other hand we cannot handle
them locally (except by the unhelpful expedient of
returning a null reference). This leaves two options:
either adding a throws clause to allow the factory method
to propagate an
SQLException
or throwing our own exception (normally one that "wraps"
the original exception).

Either approach places a burden on the client code -
which will generally be in no better position to handle the
error than the factory method itself. (Clearly, this is an
iterative arguement; but, somewhere far up the call stack
there will be some code that manages the error.)

Loss of information

The strategy of "wrapping" exceptions prior to
propagating them, alluded to in the last section, has the
unfortunate side effect of making it difficult to detect
the distinction between different problems
programmatically. Essentially, one ends up with the
situation that all that the programmer can be sure of is
that "something went horribly wrong". Admittedly, that is
often enough but it occasionally limits options. For
example, how can one decide if is it worth retrying the
operation that failed?

The alternative strategy (of allowing the original
exceptions to propagate) can lead to a similar loss of
information. Writing multiple, nearly identical, catch
blocks is tedious and a potential source of the familiar
maintenance issues caused by "cut and paste". Sooner or
later, someone just writes "
catch (Exceptions e)
" - forgetting the (usually unintended) side effect that
this also catches
RuntimeExceptions
.

Information overload

Depending upon whether exceptions are allowed to
propagate or are "wrapped" two things can happen. Either,
an increasing and incoherent set of exceptions begin to
appear in the throws clauses of methods dependent on others
- until developers gets fed up with this insanity and
introduce "
throws Exception
". Or, nearly every method has code to catch exceptions
propagated from the methods it depends upon, "wrap" them in
a new exception that makes sense within its interface and
throw the new exception.

The theory sounds bad enough but, in practice, there is
another thing that happens (although no developer admits to
doing this intentionally). That is to consume an
inconvenient exception by catching and ignoring it.

If these problems that occur in practice are not enough to
justify considering alternatives there is a further difficulty:
you may be coding to a function signature that doesn't allow
you to throw any checked exceptions. This can happen when you
are implementing an interface that you don't control (for
example
Comparator
).

Fundamental ideas

In C++ I use exceptions to report problems that it is
unreasonable to expect the immediate client code to deal with.
In particular, the example of the factory function described
above seems to satisfy these criteria: The part of the system
that knows how to deal with loss of the database connection is
likely to be many layers away from a factory function that
retrieves objects from a database.

The problems related in the last section suggest that this
approach isn't working - so either the approach is wrong or it
is being implemented incorrectly. There are many differences
between C++ and Java but there are also lots of similarities
between them. Specifically, there are enough similarities in
the exception handling mechanisms that I'd expect to use Java
Exceptions
for the same things that I'd use C++ exceptions for.

What the problems identified above illustrate is the ways in
which Java checked exceptions are not like C++ exceptions.
Declaring a checked exception places an obligation on the
caller of a method to do something explicit with the exception
- and that is precisely what isn't desired. What is desired is
to transfer program flow in an orderly manner to some point far
up the call stack.

There is something in Java that looks far more like my
familiar C++ exceptions than Java's checked exceptions: Java's
unchecked exceptions. To me they looked like the answer - it
doesn't take much thought to conclude that approaching the
above scenario by wrapping the
SQLExceptions
in an unchecked exception causes none of the above
problems.

I outlined this approach at the meeting, and there was
general consensus that it made sense, but a number of concerns
were expressed: mostly regarding the choice that exists between
the two exception-handling mechanisms. One of the senior team
members agreed to use his notes from the meeting to draft a
guideline "exceptions strategy" paper. This would be reviewed
at a subsequent meeting when everyone had had an opportunity to
think about it. (It is a good idea to review such solutions a
few days later - it is very easy to be seduced by an attractive
idea and overlook a killer issue during a brainstorming
session.) The current version of this paper is included
below.

Later on that day I was approached by one of the more
thoughtful team member who was concerned that while he couldn't
see anything wrong with what I was suggesting he couldn't find
any books - or reference material on the internet - that agreed
with it. I'm always pleased to be approached like this as I can
be wrong, and much of the value of such meetings is lost if
people don't think and research for themselves.

In this case, I view this as a reflection of the immaturity
of the Java community - the hype surrounding the language often
gets in the way of recognising an issue and finding a solution.
It took the C++ experts from 1990 (when they were introduced as
"experimental" in the ARM
[3]
) to 1997 (when the C++ standard library was revised to specify
its behaviour in the presence of exceptions) to get a consensus
on how to use exceptions.

I knew from my experience with C++ that there are ways to
write code that works in the absence of the compiler prompting
the developer to deal with exceptions. Indeed, I knew the
fundamental ideas used in managing exceptions in C++ could also
be applied to Java: I presented a translation of them at the
ACCU conference 2001
[4]
. (Anyway, it isn't the first time I've disagreed with
authority - and it surely won't be the last!)

One more problem

There was one more significant problem that had been
observed in a number of existing systems. In these, it had been
found to be difficult to handle the errors reported via
exceptions effectively. This was believed to be a result of
every type of failure reported being reported using the same
exception type - albeit with different message text. (If this
sounds to you like the
java.sql
package and ubiquitous
SQLException
then you won't be surprised that this was mentioned.) The
problem was actually worse than with the
java.sql
package since many parts of the system delivering differing
types of functionality threw this same exception, and there was
no equivalent of the well established (and fixed) set of
SQLState
values to deal with.

To address this we concluded that there needed to be
guidance covering the choice of the specific exception to use.
By requiring any exceptions specified in throws specifications
to belong to the package that propagates them we hoped to
discourage this habit. And, by checking conformance to the
guidelines as part of the class design review, we encouraged a
careful consideration of the contract between client code and
implementation.

Exceptions policy

Following the review meeting the team adopted the suggested
policy document. (It was updated to clarify it then and a
couple of times later, but has remained close to the original
discussion.) It reads as follows:

Background

There is at present no clear-cut policy for Java Exception
Handling within any of the current OPUS Java systems. This
has caused inconsistencies in the use of Exception Handling
and these have resulted in problems.

This document addresses the use of two categories of
exceptions: checked exceptions and unchecked exceptions.

Checked exceptions provide a mechanism for ensuring that
the caller of a method deals with the issue they report.
(Either by explicitly handling the exception, or by
propagating it.)

Unchecked exceptions should only be considered for
"long-distance" exception propagation. (To enable reporting
of fairly catastrophic events within the system.)

To support these options all exceptions raised within the
system will be subclasses of either
OpusException
(which extends
Exception
) or of
OpusRuntimeException
(which extends
RuntimeException
). These provided the facility to wrap exceptions.

Guidelines

It is the responsibility of the Class Designer to
identify issues that would result in a checked exception
being thrown from a class method. Those reviewing the
class design check that this has been done correctly.
Exception specifications are not changed during
implementation without first seeking agreement that the
class design is in error.

Exceptions that propagate from public methods are
expected to be of types that belong to the package
containing the method.

Within a package there are distinct types of exception
for distinct issues.

If a checked exception is thrown (to indicate an
operation failure) by a method in one package it is not
be propagated by a calling method in a second package.
Instead the exception is caught and "translated".
Translation converts the exception into: an appropriate
return status for the method, a checked exception
appropriate to the calling package or an unchecked
exception recognised by the system. (Translation to
another exception type frequently involves
"wrapping".)

Empty catch-blocks are not used to "eat" or ignore
exceptions. In the rare cases where ignoring an exception
is correct the empty statement block contains a comment
that makes the reasoning is behind ignoring the exception
clear.

In practice

This policy seems to have worked well both in terms of being
followed without much difficulty by the original team members
and for induction of new team members. Subsequently, other
teams have also adopted it. To that extent it has been a
success. However, it isn't the end of problems with the use of
exceptions.

The remaining problems are much more manageable and fall
into two categories:

Catching exceptions at too low a level - rather than
allowing them to propagate, they are caught by a piece of
code that doesn't have sufficient context to deal with them
effectively; and,

Catching too general a range of exceptions - for
example, rather than catching
OpusException
and
SQLException
separately, and handling each, there is a single catch
clause for
Exception
that then uses
instanceOf
to identify the exception type. Sometimes such code fails
to take account of the possibility of
RuntimeExceptions
- and "eats" them.

These problems are not as widespread as those reported
originally - indeed the majority of developers on the project
are unaware of them. Both of these issues reflect poor coding
technique and can be addressed by educating developers. This
education could have occurred seamlessly as part of the project
had we instituted code reviews; but I chose to postpone
introducing them in favour of other process changes.

Conclusion

Java developers are rightly encouraged to use unchecked
exceptions with caution. However, the current wisdom is too
extreme. Unchecked exceptions in Java correspond to exceptions
in C++, Smalltalk and C# - and shold be used in the same way:
sparingly.

It seems that I'm not the only one to have doubts about
this. Shortly after I wrote the first draft to this article
Kevlin Henney pointed out that Bruce Eckel had opened
discussion on the same point
[5]
. An early draft of this article has been I also opened a
discussion of this issue on JavaLobby
[6]
.