Exceptional Exceptions
Here’s a question for you: when is a checked exception not checked?
First, what is a checked exception? If you already know all about checked exceptions, feel free to skip ahead. Hey, it’s like Choose Your Own Adventure!
For those who need a refresher and would rather not dig into the link above — you have better things to do than read links in blog posts, right? — let me summarize: in the Java language, a checked exception is one that you have to either catch, or else specify that you intend to re-throw. Checked exceptions are used:
- In cases where an error condition is foreseeable, so the program can do something to recover from it
- In job interview questions.
When you call a method that declares it throws a checked exception, you are getting an offer from the Don.
“I’m going to make him an offer he can’t refuse.” – in this case, a checked exception.
Your code won’t even compile if you don’t either catch the checked exception, or specify that you’ll re-throw it.
For example, the code below is calling a method appropriately called callMethodThatThrowsIOException
. Since the called method throws an IOException
, you have to deal with it somehow, and here’s how Eclipse complains if you don’t:
The javac
compiler would complain in a similar way to Eclipse. The solution is to do either of the things Eclipse suggests — either declare that you throw the exception from your method, thereby passing the problem upstream, or catch the exception and deal with it. That may involve logging a message, recovering in some way, or swallowing the exception and doing nothing, if you’re evil and enjoy torturing operations staff.
Here’s how the Operations staff feel if you swallow exceptions silently:
“Why you hide errors from us?!”
By way of contrast, unchecked exceptions, which by definition are those derived from RuntimeException
, don’t have to be caught or specified by you. A very popular example is the beloved NullPointerException
. This exception exists to remind you that in your hurry to get to Happy Hour, you forgot to add that null
check before using an object reference.
Incidentally, the whole idea of whether checked exceptions should even exist is a controversial topic, leading to much wailing and gnashing of teeth, and “holy wars” on programming forums. Google for “java checked exceptions”, without the quotes, for a fun time.
Back to the original question. When is a checked exception not a checked exception?
Rather than drag this out through six seasons like the producers of Lost (only to disappoint you in the end…grrrr) I’ll tell you up front: a checked exception can “unbecome” checked when Scala is in the mix.
Post-Java languages like Scala, Clojure and Groovy care not a jot for checked versus unchecked exceptions, which pretty much exist only in the Java compiler’s fevered imagination, so when you add a tasty creamy filling of Scala code in between your Java layers, the checked-ness of the exceptions may evaporate.
Let’s look at some code. This Scala object implements java.io.reader
, a Java interface which includes a few methods that should declare that they throw checked exceptions.
package com.rr import java.io._; object CheckedExceptionsDemo extends Reader { // Not declared or thrown override def read(cbuf: Array[Char], off: Int, len: Int): Int = { 0 } // Thrown, but not declared. Bad Scala programmer! override def close() = { if ( Math.random() > 0.5 ) throw new IOException("This should never happen since I don't declare this can be thrown"); } // Declared, and thrown. Good and wholesome. @throws(classOf[IOException]) override def read(cbuf: Array[Char]): Int = { throw new IOException("Don't be surprised. I told you I might throw this!"); } }
The read method on line 7 doesn’t declare that it throws anything, so a Java client consuming this method will not be allowed to catch IOException
, since the Java compiler (rightly) concludes that the Scala method doesn’t throw any checked exceptions, in flagrant violation of the contract defined by the Reader
interface.
The close
method on line 12 is even naughtier. Not only does it fail to re-declare the checked exception from the Reader
interface, it brazenly does throw an IOException
. Yet a Java client will not be allowed to catch this exception either since it’s not declared in the method signature.
The read method on line 18 does the right thing, by declaring the exception to be thrown, in line with the contract of the Reader
interface, using @throws(classOf[IOException])
This means a Java class consuming this method will be able to catch and handle the IOException
.
In short, Scala doesn’t care about checked exceptions — that’s just how it rolls.
Checked exceptions – Scala don’t give a…well, you get the idea.
Here’s what happens when you try to consume these various methods from Java. Examine the comments for each method to see what happens.
// Call a Scala method that declares the checked exception properly public void method1() { char[] cbuf = new char[100]; try { CheckedExceptionsDemo.read(cbuf); // Compiles fine } catch (IOException e) { } } // Call a Scala method that doesn't declare or throw the checked exception public void method2() { try { char[] cbuf = new char[100]; CheckedExceptionsDemo.read(cbuf, 0, 1); // Doesn't compile - // "Unreachable catch block for IOException. This exception is never thrown from the try statement body" } catch (IOException e) { } } // Call a Scala method that throws the checked exception but fails to // declare it public void method3() { try { CheckedExceptionsDemo.close(); // Doesn't compile - // "Unreachable catch block for IOException. This exception is never thrown from the try statement body" } catch (IOException e) { } try { CheckedExceptionsDemo.close(); // Compiles fine since you're always allowed to catch Exception. // We have to sniff out the exception type and cast it } catch (Exception e) { if (e instanceof IOException) { IOException ioe = (IOException) e; // do whatever I would normally do with an IOException // ... } } }
In summary: Scala programmers are on the hook to re-declare any checked exceptions in APIs they implement, otherwise Java consumers will not be able to catch those exceptions. Scala can “hide” the “checkedness” of exceptions from Java, if you want that behavior. This is documented in a rather obscure place, but I personally found this behavior rather unexpected, no doubt due to my background being mainly Java rather than Scala.