Messy Exception Hierarchy

[Voting on JavaDesignFlaws page.]

Java's exception hierarchy is very messy.

A better hierarchy would be something like:

  Throwable
  +-UncheckedThrowable
  | +-RunTimeError (failure of the JVM or other unrecoverable error)
  | +-LogicalError (programmer errors)
  |	+-NullPointerError
  |	+-PreconditionViolatedError
  |	| +-IllegalArgumentError
  |	| +-IllegalStateError
  |	+-PostconditionViolatedError
  |	+-InvariantViolatedError
  |
  +-CheckedThrowable (exceptions caused by the program's environment)
	+-AvailabilityException
	+-PermissionException
	+-ResourceExhaustionException
	+- ... other generic categories of environmental exception ...

A "ResourceExhaustionException" can in practice occur at any time and is unrecoverable for the thread in which it occurs, therefore it should be a RunTimeError.

The Java and C++ exception hierarchies suffer from SpaghettiInheritance and PrematureGeneralization.

Consider blocks of exception handlers. There are only two kinds:

But those 'few interesting exceptions' are very rarely the same as any subclass of exceptions the library provider has included.

--MattRickard

One problem with Java is that it enforces exception handling, which means often you have to catch exceptions where you don't know what to do. Because Java doesn't manage resources well, this also means you have to catch exceptions where you don't want to. Often, you just want to catch all exceptions, rollback the transaction, and then fail quietly. It doesn't matter what the exception is normally. C++ also suffers from this, but less so because you don't have to catch exceptions and you have resource management and you can throw anything (not just subclasses from Throwable). Perl and some Smalltalks (and apparently some Lisps) do this better by raising untyped exceptions. e.g. die in Perl, which is catchable by an eval{}. -- SunirShah

I had the following pointed out to me elsewhere. Apparently, the JTAPI framework is full of methods like this:

   public Connection[] connect(Terminal origterm,
                               Address origaddr,
                               java.lang.String dialedDigits)
                      throws ResourceUnavailableException?,
                             PrivilegeViolationException?,
                             InvalidPartyException?,
                             InvalidArgumentException?,
                             InvalidStateException?,
                             MethodNotSupportedException?;

All checked exceptions. All without any common ancestor less general than Exception. Eugh. --CharlesMiller


How about an exception hierarchy is of very dubious value and probably shouldn't be there at all? --SomebodyElse?

Although I added this page, I agree with this statement! Logical errors should be handled by dumping core so that the program can be examined in a post-mortem debugger. I have never had to write code that reacts to specific types of environmental exception. Instead code handles all exceptions in the same way by catching the most general exception class that is applicable. I think this is because exceptions thrown by Java APIs are almost always defined to be derived from a per-package base class (SQLException for java.sql, JMSException for javax.jms, and so on), rather than being derived from base classes that categorise the type of error (missing resource, invalid permissions, communication failure, etc.)


This has been a bit of an eye-opener for me. I'm finally realising why I'm doing what I've been doing with my exception handling over the past year.

I've ended up creating an unchecked SystemFailureException? to which one can chain other exceptions (lack of a standard method of exception chaining was a frequent complaint about Java, fixed by JDK 1.4 ChainedExceptions), and when I encounter an environmental error (database goes down), I pack up whatever exception I got that indicated this and then use the SystemFailureException? to carry it up to the top level, where it gets dealt with (by paging me). For "things that can't happen" I generally just throw an Error.

I disagree with the "dump core on logical fault," at least if it can't be disabled. I'd hate to see my entire website go down because of a logical error in one request. Better to log it and keep running. (Of course, if it's in a frequently occurring piece of code, you're not likely to be doing much other than throwing exceptions.) --CurtSampson

If you have a programming error, you cannot just keep running because the logical error will have left the program internals in an unknown state. Continuing execution may corrupt existing data or cause invalid data to be fed to the rest of the system. There are techniques that are used to implement ByzantineFault tolerance even in the face of programming errors -- "byzantine generals" consensus algorithms for example -- but catching a logical exception and continuing execution is not one of them. --NatPryce

Hm. So what you're saying is that, when due to a programming error my servlet engine fails to generate one page, and aborts on it, the entire VM must shut down because the system is in an "unknown state"? I don't know whether this is "theory is nice but not as useful as reality" (i.e., my users can still use most of the site, even if they can't use that page), or whether I just misunderstand what you're trying to say. --cjs

In the case of an error in a servlet the request is in an unknown state and is immediately terminated. The servlet engine as a whole is in a known state and so is the page (since pages in general don't even have a state). --AndersBengtsson

This is very probably a desirable property of a servlet-based application. It isn't a necessary property of all possible servlet-based applications. -- LaurentBossavit

If you modularise your system into components and properly isolate those components from one another then you can shut down an individual component when it fails due to a programming error, and let the rest of the components keep running. An example is an operating system using protected memory to ensure that a bug in one program does not bring down other running programs or the entire operating system.

--NatPryce


In Java, it's fairly difficult to do transaction management. In C++, by using constructors and destructors judiciously, it was (relatively) easy to create a transaction-based (read: event-driven) soft real-time system. For exceptions to work effectively in Java, they need a better way of rolling back on errors then littering code with try{}catch{}s. In fact, C++ is the only language I know of that does this well. Can anyone point to others? -- SunirShah

We use a variation of the ResourceReleasesResource idiom to handle failover in a group-oriented distributed system. The failover protocol calls into the application through an interface that can return successfully or throw a failover exception (which itself is a chained exception). Implementations of the interface propagate exceptions up to the top level, where they are converted into failover exceptions. The implementation of the failover protocol implements how to react to the failure. --NatPryce


See also: LimitsOfHierarchies / MessyExceptionHierarchyAlwaysHappen


CategoryException | CategoryJava

EditText of this page (last edited June 11, 2005) or FindPage with title or text search