Skip to main content

Java Exception Handling Patterns (Part 1)

Posted by bakksjo on September 19, 2005 at 6:22 AM PDT

In a series of blog entries, I intend to discuss various patterns for exception handling in Java.

Don't use enumerated exceptions


Quite often, you will see code using what I like to call "enumerated exceptions".
An enumerated exception typically looks like this:


public class ModuleException
   extends Exception
{
   private int exceptionNumber;

   public final static int EXCEPTION_A = 1;
   public final static int EXCEPTION_B = 2;
   public final static int EXCEPTION_C = 3;

   public ModuleException(int errcode)
   {
      exceptionNumber = errcode;
   }

   public int getErrorCode()
   {
      return exceptionNumber;
   }
}


Problem 1: Over-declaring exceptions


Consider a method which is declared like this:


public void myMethod
   throws
      ModuleException
{
   ...
}


To someone who does not completely know the internals of myMethod, there is no way to
safely interpret this declaration other than as the logical equivalent of this:


public void myMethod()
   throws
      EXCEPTION_A,
      EXCEPTION_B,
      EXCEPTION_C

{
   ...
}


As such, code which calls myMethod must, in principle,
check for all these exceptions, like this:


try {
   myMethod()
} catch (ModuleException e) {
   switch (e.getErrorCode()) {
   case ModuleException.EXCEPTION_A: ...;
   case ModuleException.EXCEPTION_B: ...;
   case ModuleException.EXCEPTION_C: ...;

   }
}


This is clearly cumbersome and wasted if myMethod only throws
ModuleExceptions with errorcode A, but there is no way to
declare that it does not throw type B or C.


However, nobody ever writes such exception handlers, for a number
of reasons:


Applicability: More often than not, there is no logical or
natural way to handle some of these exceptions in the current
context, since exceptions A, B and C may be of completely different
nature and occur in distinct places.


Volume of single catch-handler: Enumerated exceptions
often contain hundreds of possible values, which makes it
incomprehensible to write a handler for each one after every call of
every method which declares that it throws an enumerated exception.


Number of handlers to write: In modules which use
enumerated exceptions, it is often practice that almost every method
in the module declares that it throws the enumerated exception, and usually only
this one. As a consequence, almost each method called from client
code will possibly have to catch the enumerated exception, so using the
principle outlined above would turn the majority of the client code into exception
handling. Even worse, most of the exception handling code would, in
practice, be produced by copy-and-paste, and often incorrect in the
current situation. Nothing would be gained except making the code
completely unreadable.


Maintainability: Whenever somebody adds EXCEPTION_D
to ModuleException, each existing exception handling
block should, in principle, be edited to include a handler for the
new exception.


For these reasons, catch handlers for enumerated exceptions hardly ever test for all possible error codes;
instead, people write their exception handlers to handle a subset of
the error codes in ModuleException: the error codes they "know"
that the method uses. This brings us over to the next problem:


Problem 2: Disabling compiler checking of
exception handling completeness


A programmer saying that she knows which error codes in an
enumerated exception are relevant after a certain method call is like
saying that she knows what exceptions a method throws if the Java
language did not require you to declare them and the compiler did not
check that they were handled.


Most Java programmers have experienced the following scenario: You write a
new method or modify an existing one. You call methods in various
libraries; say, e.g., container-related methods from the java.util package.
You read the javadoc for this library to make sure you understand how
the methods work, what input parameters they take, what their return
values are. You think your modifications are complete, so you compile and test your program - and then you find that the compiler
complains to you that you have uncaught exceptions; you need to either handle a certain exception
or declare that your method throws it.


The exception was written in the documentation, but you missed it and did not
write a handler for it anyway. Not until the compiler told you that
you needed to do so. Only then you sat down and asked yourself: "What
should my program do when that exception is thrown?"


Using enumerated exceptions effectively disables compiler checking
of the completeness of your exception handling.
This is extremely
dangerous and nonchalant! Consider the following code:


public void methodA()
   throws
      ModuleException
{
   ...
   throw new ModuleException(ModuleException.EXCEPTION_C);
   ...
}

public void methodB()
{
   try {
      methodA()
   } catch (ModuleException e) {
      switch (e.getErrorCode()) {
      case ModuleException.EXCEPTION_B: doSomething();
      }
   }
}


Here, the programmer of methodB thinks that methodA
only throws EXCEPTION_B. There is no handler for
EXCEPTION_C, which is actually thrown (maybe it was added after was written), and the result
may be disastrous, but the compiler will be happy.


Consider instead:


public void methodA()
   throws
      ExceptionB,
      ExceptionC

{
   ...
   throw new ExceptionC();
   ...
}

public void methodB()
{
   try {
      methodA()
   } catch (ExceptionB e) {
      doSomething();
   }
}


The compiler will complain to you, since ExceptionC is not caught and not declared to be thrown in methodB.


Observe that in the first example, declaring that methodB
throws ModuleException does not help - EXCEPTION_C
will not be passed on to the caller of methodB anyway. In order to pass it on,
it has to be explicitly tested for in the switch block
and re-thrown, whereas in the last example, one can add ExceptionC to the throws clause of methodB and force the caller of methodB to handle it.


Problem 3: Polluting the exception-class with
non-relevant fields and methods


Often, one will want to add fields or methods to some exception
types. With distinct classes for each exception type, this is no
problem. With enumerated exceptions, it usually means adding methods
and fields which are relevant for some error codes, and have no
meaning for others. Quite often these classes end up somewhere in the
middle - they try to restrict the level of pollution, so they don't
go "all the way" in providing all possible information that
could be supplied with single error codes. This causes them to be
neither "pure" and general, nor as specific as they should be for a particular case. This
should be a clear indication to the programmer that enumerated exceptions
are a bad idiom, and one should rather use multiple, separate
exception classes.


You will see numerous modules using this pattern, such as, for
instance, JDBC:


try {
   // execute SQL statement
} catch (SQLException e) {
   if (e.getSQLState().equals("40001")) {
      // Terminated because of deadlock
   }
}


If the exception above was instead a separate class (say, SQLDeadlockException), it could contain more information, e.g. what locks the statement could not contain.


The fact that the pattern is commonly used in "big" libraries/APIs does not make it a
better pattern. All drawbacks listed above apply to all the modules which use
this pattern. In JDBC, for instance, developers typically "forget"
to write code to handle retryable exceptions. The compiler forces
them to catch a SQLException, but since they have little or no idea
up-front of what kind of errors this exception may be, they don't
write the if ()-test in the catch handler and simply "give up" executing the statement.


By now I should have convinced you not to use enumerated exceptions, right? ;o) So, what should you do then? General rule: Write separate classes for all your exceptional conditions. Declare for each method exactly which of these are thrown. Do not declare to throw some big, fat, one-exception-to-rule-them-all.


Stay tuned for part 2: Objections to this rule, adaptations you can make in certain cases where this simple advice may not suffice.

Related Topics >>