This blog post is intended for new Java developers. It starts with a historical perspective and a look at what motivated the design and creation of Java’s exception handling mechanism. It also explores the hotly debated checked vs unchecked exceptions debate with some personal insights.
Let’s start.
Historical Perspective
Back in the time of the “C” programming language, it was customary to return values such as -1 or NULL from functions to indicate errors. This was practical for small applications but didn’t scale well for larger applications - developers had to check and track every possible return value: a return value of 2 might indicate ”host is down” error in library A whereas in library B, it could mean ”illegal filename”. Although, developers tried to fix this and attempts were made to standardize error codes by setting global variables, but it didn’t help much.
James Gosling and other designers felt that a similar approach would go against the design goals of Java programming language. They wanted:
- a cleaner, robust and portable approach
- built-in language support for error checking and handling.
Essentially, one of their main design goals was to build a language that’s robust and able to cope with errors during execution or at least recognize when going go wrong. This principle is at the core of Java’s error handling design, as we’ll see later. James Gosling explains in one of his interviews:
One of the traditional things to screw up in C code is opening a data file to read. It’s semi-traditional in the C world to not check the return code, because you just know the file is there, right? So you just open the file and you read it. But someday months from now when your program is in deployment, some system administrator reconfigures files, and the file ends up in the wrong place. Your program goes to open the file. It’s not there, and the open call returns you an error code that you never check. You take this file descriptor and slap it into your file descriptor variable. The value happens to be -1, which isn’t very useful as a file descriptor, but it’s still an integer, right? So you’re still happily calling reads. And as far as you can tell, the world is all rosy, except the data just isn’t there.
They didn’t have to look too far. The inspiration for handling errors came from a very popular language of the 60’s: LISP. Java’s exception handling was born and (for better or worse) the rest is history.
Exception Handling in Java
So what is exception handling? It is unconventional but a simple concept: if an error is encountered during program execution, halt the normal execution and transfer control to a section specified by the programmer. Let’s look at an example:
try { f = new File("list.txt"); // throw error if file is not found... f.readLine; f.write("another item for the list"); f.close(); } catch (FileNotFoundException fnfe) { // transfer control to this block on error. // do something with the error. // notify user or try reading another location, etc }
In other words, exceptions are exceptional conditions which disrupt the normal program flow. Instead of executing the next instruction in the sequence, the control is transferred to the Java Virtual Machine (JVM) which tries to find an appropriate exception handler in the program and transfer control to it (hence disrupting the normal program flow). In the last example, if the file list.txt
is not found, the control will be transferred to the catch(...)
block instead of continuing to the next line in the try
block. Here are some more examples where an exception can be thrown:
- Accessing index outside the bounds of an array
- Disk is full
- IP address for a host couldn’t be determined
- Using a
null
value when an object is required - Divide by 0
- Violation of defined contract: e.g. invalid values passed to a method
Two Types of Exceptions in Java: Checked and Unchecked
In Java, there are two types of exceptions: checked and unchecked. Let’s take a look at them.
Checked Exceptions
Checked exceptions are used to represent recoverable error conditions e.g. file not found. Java requires that these exceptions are explicitly handled by developers or the code won’t compile. According to official documentation:
These are exceptional conditions that a well-written application should anticipate and recover from. For example, suppose an application prompts a user for an input file name, [..] But sometimes the user supplies the name of a nonexistent file, and the constructor throws java.io.FileNotFoundException. A well-written program will catch this exception and notify the user of the mistake, possibly prompting for a corrected file name.
Omitting to catch a checked exception will result in a compile time error:
Main.java:8: Warning: Exception java.io.FileNotFoundException must be caught, or it must be declared in throws clause of this method. f = new FileInputStream(filename); ^
Developers have a few options on how to handle checked exceptions. All of these require an explicit acknowledgement of the exception and taking an action on it.
- catch and do something with the exception, or,
- re-throw it and give the methods higher up in a call trace a chance to handle it, or,
- catch the exception, wrap it up in a different exception and throw it up. This is called exception wrapping.
Since checked exceptions represents problems from which a program may wish to recover, the designers of Java wanted to force developers to at least pay attention so that these do not get accidentally ignored. (and are handled as close to the source as possibe.) As James Gosling explained:
… because the knowledge of the situation is always fairly localized. When you try to open a file and it’s not there, you’re coping strategy is really determined by what you were going for. Some guy miles away isn’t going to know what to do. The code that tried to open the file knows what to do, whether it be trying a backup file, looking in a different directory, or asking the user for another filename.
You can’t accidentally ignore a checked exception. If you do choose to ignore, you must do so explicitly.
try { Set set = ... // code which throws checked exceptions } catch (Exception e) { // do nothing or "I don't care". Extremely bad practice. }
Beware: Do NOT do this in real-life. It’s an extremely bad practice. At the very least, log the exception.
Unchecked exceptions
The documentation says:
(unchecked exceptions) are exceptional conditions that are internal to the application, and that the application usually cannot anticipate or recover from. These usually indicate programming bugs, such as logic errors or improper use of an API.
Unchecked exceptions (aka RuntimeExceptions) represent problems which happen during program execution e.g. divide by 0, accessing object method on null
object reference, etc. Unlike checked exception, Java doesn’t require that we catch unchecked exceptions and the compiler won’t complain. These can happen anywhere in your program and our code will be littered with try-catch
if we were catching every RuntimeException. In real life this would be equivalent of putting diesel into a gasoline car. You broke the contract and shouldn’t have done it. A mistake was made. The car comes to a grinding halt. There’s nothing you can do to fix it and get to your destination. You must to take the car to a dealer.
Should you catch RuntimeExceptions?
What’s the point of catching RuntimeExceptions if the condition is irrecoverable? These errors are usually preventable by fixing your code in the first place. For example, dividing a number by 0 generates RuntimeException (ArithmeticException.) But you could avoid it by checking that the argument is greater than zero i.e. denominator > 0
. If this condition is not true, halt further execution. (and possibly throw IllegalArgumentException!)
But there are situations when it’s alright to catch RuntimeExceptions, log the error and move on. A while ago, I worked on a high-throughput client that was processing thousands of transactions a second. If a transaction was malformed, the code will complain and throw RuntimeException. To prevent that from happening, we were checking every single transaction to catch and ignore any malformed transactions.
boolean isMalformed(Transaction t) { // Check transaction and return false if it's malformed; true otherwise }
When we looked at the metrics, it showed that malformed transactions were super rare. 99.99% of the transactions were good, yet we were wasting precious CPU cycles testing every single transaction (including String comparison on some of the fields). So we removed the isMalformed(...)
method and let the code throw RuntimeException, log it and move on to the next transaction. The result was improved application performance.
Checked vs Unchecked Exceptions
Many people find the dichotomy between checked and unchecked exceptions confusing and counter-intuitive. The core argument is whether or not a language should force developers to catch exceptions. Other languages like C++ and the more modern C#, left out checked exceptions and only support unchecked exceptions. If you want to read up on the debate, you can visit this Stack Overflow question.
Both sides have equally compelling arguments. Personally, I use checked exception when coding in Java but only judiciously and where it makes sense. I use them so that callers of my code don’t accidentally ignore something which they shouldn’t. And this is how it’s supposed to be done. I have worked with code where developers who didn’t like checked exceptions were disguising them as unchecked (creating all exception subclasses from java.lang.RuntimeException
.) This is dangerous because some users of their code or library (other Java developers) who are expecting to only handle checked exceptions may not pay attention to unchecked and ignore things which they shouldn’t. On the other extreme, there are developers who just don’t understand when to use which type and force others to catch ‘checked’ exceptions which they cannot recover from, causing pain and displeasure. A developer I knew would Google search for similar built-in exception types and blindly use or extend from what he found without stopping and thinking whether he should be using checked or unchecked. And that I feel is the root cause behind the confusion and the debate: checked exceptions aren’t bad. People just don’t pay attention and choose unwittingly.
If you are in the camp of developers who don’t like checked exceptions and wish they go away, you should know that it’s not going to happen any time soon. Oracle is still promoting the use of checked exceptions as is evident from any Java documentation. Joshua Bloch also supports the use of checked exceptions in his seminal book Effective Java:
Item 40: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors. E.g. host is down, throw checked exception so the caller can either pass in a different host address or move on to something else.
Item 41: Avoid unnecessary use of checked exceptions.
Good or bad, checked exceptions are here to stay and and while they are not perfect or even good, we should use them as intended when programming in Java. Enough said. Let’s move on take a look at the exception class hierarchy in Java.
The Java Exception Class Hierarchy
All exceptions in Java have a common ancestor: java.lang.Throwable
. The following base exception classes are most commonly used by Java developers:
1. java.lang.Exception
and the java.lang.RuntimeException
Any class extending from java.lang.Exception
is classified as checked exception and must be declared in a method’s throw
clause. Likewise, any class extending from java.lang.RuntimeException
is classified as unchecked exception.
Confusingly enough, java.lang.RuntimeException
is a child of java.lang.Exception
but it is classified as unchecked exception. (Special case.)
2. java.lang.Error
Error
and its subclasses are the second category of unchecked exceptions (the first one being the RuntimeException
). These are used to indicates serious or abnormal problems e.g. disk failing while your application is in the process of writing to it or VirtualMachineError etc. Your application cannot recover from these errors and for practical purposes, you’d never have to catch or worry about this type.
Here’s a look at the Java Exception class hierarchy visually:
That’s all folks! Hope you found this useful.
Here’s a more recent talk on the subject by Elliotte Rusty Harold, author of several Java books. I think he completely nails the issue that checked exceptions are not bad, it is just that “Sun forgot to tell anybody how to use them!”.