Exceptions

When bad things happen to good programs.

The need for exceptions

In the normal course of execution, methods call other methods, which call other methods.

Normally those methods return and the stack unwinds with any returned values being returned back up the stack from called methods to their callers.

However

Sometimes things go wrong and a method can’t do what it’s supposed to do.

Could be something completely outside the program

A file you need to open doesn’t exist.

You need to make a network connection but the network is down.

Or it could be internal to the program

Remember invariants?

Preconditions

The things that must be true in order for a method to work.

Postconditions

The things that must be true after the method has been called.

(Unless the precondition wasn’t satisfied. Then all bets are off.)

Class invariants

Anything that has to be always true about instances of the class.

With the slight caveat that while a method is running, the class invariants may be briefly violated.

But they must be restored before the method returns.

And you need to be careful about calling other methods while any invariant is perturbed.

Start with the happy path

The call stack

foo() bar() baz() quux()

Calls go down, return values come back up.

When something bad happens

Suppose the quux method can’t do what it’s supposed to do so can’t return the value it is supposed to compute.

Which means baz, who called it, is also outta luck.

In Java we deal with that by throwing an exception

private int quux() throws MyException {
  if (cantDoTheThing()) {
    throw new MyException("Something bad happened");
  }
  return computeValue();
}

That looks like this

foo() bar() baz() quux() Boom!

As it stands, this will crash your program (or thread).

But Java gives us a way to deal

private int bar() {
  try {
    var x = baz();
    return frob(x);
  } catch (MyException me) {
    return BAR_DEFAULT_VALUE;
  }
}

We’re assuming baz has no way to deal with the exception and therefore also couldn’t return the value we needed but at this level we have a way to recover, such as by returning a default value.

That looks like this

foo() bar() baz() quux()

bar catches the exception, stopping it from propagating.

Then bar returns normally and foo is none the wiser.

Java language constructs

throw

throw new SomeException("A message");

This is how we throw an new exception.

Typically the thing after throw is an expression constructing a new exception object but it can be anything that evaluates to an expression.

try/catch

try {

  // Code that might throw an exception

} catch (SomeException e) {

  // Code to deal with the exception

}

try/catch/finally

try {

  // Code that might throw an exception

} catch (SomeException e) {

  // Code to deal with the exception

} finally {

  // Code that runs regardless of whether the try block
  // threw or completed normally. Even runs if the code
  // in the try block contains a return statement.
}

Can also use try/finally with no catch to make sure some cleanup code runs.

try with resources

try (FileReader fr = new FileReader(path);
     BufferedReader br = new BufferedReader(fr))
{
  // code that uses the resources declared above
}

Makes sure the resources are properly closed after the code in the try block runs, regardless of whether any exceptions were thrown, either in the block or when trying to create the resources.

Exception hierarchy

Throwable

The root of the hierarchy of things that can be used with throw and catch.

You will almost never refer to Throwable directly. I.e. you won’t construct a Throwable, catch Throwable, or subclass Throwable directly.

Throwable holds a special place in my heart because it’s the one place I left a mark on the language as I convinced them to add the getCause() method.

Error

Errors are for indicating things that have gone badly enough that the program probably cannot be expected to recover.

AssertionError

This is the error to throw when an invariant is violated.

An invariant being violated is due to a bug in the program.

  • A caller failed to establish a precondition

  • A method failed to establish its postcondition despite its precondition being satisfied

  • An object got into a state that violates the class invariant.

VirtualMachineError

“Thrown to indicate that the Java Virtual Machine is broken or has run out of resources necessary for it to continue operating.”

Subclasses:

  • OutOfMemoryError

  • StackOverflowError

  • InternalError

  • UnknownError

Exception

Exceptions are typically used to indicate that something out of the program's control has prevented it from doing what it’s supposed to do.

Classes descended from Exception that do not descend from RuntimeException are called “checked exceptions” and must be declared to be thrown by any method that can throw them directly or indirectly.

Checked exception

public void frob(Thing t) throws FrobnicationException {
  // whatever
}

The throws clause declares that it’s possible that this method will throw a FrobnicationException.

Callers of this method must either declare that they also throw it or must catch it.

Throwing

public void throwIt() throws FrobnicationException {
  frob(someThing);
}

This method calls frob and has no way to recover if frob fails so it has to let it propagate.

Catching

public void catchIt() {
  try {
    frob(someThing);
  } catch (FrobnicationException fe) {
    // recover somehow
  }
}

On the other hand, if it’s possible to recover, you should.

Exceptions and abstraction

Exceptions are part of a class’s API and therefore should be at the same level of abstraction.

Don’t leak implementation details by letting low-level exceptions propagate out of your methods.

Wrapping exceptions

public void highLevelMethod() throws HighLevelException {
  try {
    someLowLevelThing();
  } catch (LowLevelException lle) {
    throw new HighLevelException(lle);
  }
}

Best of both worlds.

Exception thrown is appropriate to the abstraction.

But for debugging purposes, it carries with it the underlying cause.

This is my feature!

RuntimeException

Exceptions that descend from RuntimeException are called “runtime” or “unchecked” exceptions.

Unlike checked exceptions they don’t need to be declared in a throws clause and typically aren’t.

They are mostly used for things that can go wrong in so many places that almost all code would have to declare them if they were checked.

Some examples

ArithmeticException int division by zero
IndexOutOfBoundsException Accessing a collection with an invalid index.
ArrayIndexOutOfBoundsException Accessing an array with an invalid index.
StringIndexOutOfBoundsException Accessing a String with an invalid index
NullPointerException Trying to invoke a method or access a field on null.

Were checked exceptions a mistake?

Possibly.

Kotlin, an “improved” Java treats all exceptions as unchecked.

The main issue is that there are usually a bunch of layers between were the exceptions is thrown and where it can reasonably be handled and it’s not clear that annotating all those layers with throws clauses helps much.