Exception Handling

Handle and Declare Pattern

  • Catching Exception and rethrowing is not really catching any Exception
void f() throws IOException {
    try {
        throw new IOException();
    } catch (Exception e) {  // Not really!
        throw e;             // Due to this guy here!
    }                        // Actually catching only types declared in method signature
}
  • Note how it looks like we are catching and re-throwing an Exception
    • In reality we are only catching and re-throwing an IOException
    • If we were actually catching and re-throwing an Exception the code would not have compiled since method is declaring only an IOException
    • Since the code compiles fine, we can assume that the compiler knows we mean we only want to catch the declared Exception type(s)
  • The following code does not compile, where we in fact are throwing an Exception
    • This is not allowed, you can not declare a narrow type and throw a broader type
    • Remember IOException is narrow compared to Exception
// does not compile
void f() throws IOException {
    throw new Exception();
}

How This Feature Prevents Compilation Failures

Imagine the following scenario:

class A extends Exception {}
class B extends Exception {}

void foo() throws A, B {}

void bar() throws A, B {
    try {
        foo();
    } catch (A | B e) {
        throw e;
    }
}
  • We did not use catch (Exception e), instead we used catch (A |B e)
  • Imagine foo gets less brittle by not throwing B anymore
    • Signature becomes void foo() throws A
  • This breaks the compilation because bar is now catching B which is never thrown in foo
    • Catching a checked type (exception of the big Exception class) is not allowed, if that particular type or a subclass is never thrown
    • Further reading here
  • By using catch (Exception e) where we still have throws A, B in method bar, we are only left with throws B which is never thrown in bar
    • That is not a compilation error cause, it is just an info
void foo() throws A {}

void bar() throws A, B {     // B is never thrown, but it is legal to declare
    try {                    // At least compile does not fail just because foo removed throws B
        foo();
    } catch (Exception e) {  // Remember catch (A | B e) was causing a compilation error
        throw e;
    }
}

Exceptions in Inheriting Methods

  • Inheriting method does not need to declare the Exception from parent if no Exception is actually thrown
class MyException extends Exception {}

class A {
    void a() throws MyException {};
}

class B extends A {
    public void a() {}  // no need for throws MyException
}
  • Inheriting methods can not declare broader or new checked Exceptions
    • However declaring unchecked Exceptions is allowed
class A {
    void f() {}
}

class B extends A {
    void f() throws RuntimeException {}  // this is fine
                                         // throwing a CheckedException would not compile 
}

Calling Method Must Handle Exception Based on the Reference Type

A a = new B();
try {
    a.a();
} catch (MyException e) { // Must handle exception
    e.printStackTrace();  // Call is on type A not B
}