Exceptions: Don’t get thrown for a loss
Catch the differences between checked and runtime exceptions
February 8, 2002
Q: Please explain the difference between checked exceptions and runtime exceptions? When would I throw them, and when would I catch them?
A: Java provides two main exception types: runtime exceptions and checked exceptions. All checked exceptions extend from java.lang.Exception
, while runtime exceptions extend from either java.lang.RuntimeException
or java.lang.Error
.
Two aspects differentiate checked exceptions from runtime exceptions: mechanical and logical.
The mechanics
From a mechanical viewpoint, runtime exceptions and checked exceptions differ in how you declare the method that throws the exception, and how you handle the exception when it’s thrown. Consider the following checked exception definition:
public class CheckedException extends Exception {
public CheckedException() {}
public CheckedException( String message ) { super( message ); }
}
The following class has a number of methods that throw exceptions:
public class ExceptionalClass {
public void method1() throws CheckedException {
// ... some code
throw new CheckedException( "something bad happened" );
}
public void method2( String arg ) {
if( arg == null ) {
throw new NullPointerException( "arg passed to method2 is null" );
}
}
public void method3() throws CheckedException {
method1();
}
}
Right away you’ll notice that both method1()
and method2()
throw exceptions, but only method1()
has an exception declaration. You’ll also notice that method3()
doesn’t throw an exception itself, but its signature indicates that it may throw a CheckedException
. Before I tell you why, consider the following main method:
public static void main( String [] args ) {
ExceptionalClass example = new ExceptionalClass();
try
{
example.method1();
example.method3();
}
catch( CheckedException ex ) {
}
example.method2( null );
}
When a call is made to method1()
, you must make the call from within a try/catch block. In this case, it’s a checked exception because you must catch it. Checked exceptions are always declared as thrown in the method signature. The signature lets the method’s caller know that an exception may occur as a consequence of the call. If the exception does get thrown, the caller must be prepared to do something about it.
Contrast method1()
with method2()
. When you make a call to method2()
, you don’t have to do so within a try/catch block. Since you do not have to catch the exception, the exception is said to be unchecked; it is a runtime exception. A method that throws a runtime exception need not declare the exception in its signature.
Now, look back at method3()
. It makes a call to method1()
without making the call in a try/catch block. method3()
avoids not catching the exception by declaring that it may also throw the exception thrown by method1()
. Instead of catching the exception, method3()
simply passes the exception along. Analogously, you could have dispensed with the try/catch block in the main method by declaring that it throws CheckedException
. (I only mention this to give you a second reference point; you should never declare a main as throwing an exception.)
Here’s a summary of the mechanical aspects of exceptions:
- Runtime exceptions:
-
- A method signature does not need to declare runtime exceptions
- A caller to a method that throws a runtime exception is not forced to catch the runtime exception
- Runtime exceptions extend from
RuntimeException
orError
- Checked exceptions:
-
- A method must declare each checked exception it throws
- A caller to a method that throws a checked exception must either catch the exception or throw the exception itself
- Checked exceptions extend from
Exception
The logic
From a logical viewpoint, checked exceptions and runtime exceptions serve two separate purposes. Checked exceptions indicate an exceptional condition from which a caller can conceivably recover. Conversely, runtime exceptions indicate a programmatic error from which a caller cannot normally recover.
Checked exceptions force you to catch the exception and to do something about it. Take a look at the constructors for java.net.URL
. Each constructor may throw a MalformedURLException
, an example of a checked exception.
Imagine that you’ve written a simple program that prompts the user for a URL. Using that URL, the program retrieves a page. If something is wrong with the URL the user enters, the constructor will throw an exception. Since the URL
constructor throws a checked exception, the program must catch the exception and try to recover. In the case of the bad URL, the program could ask the user to retype it.
Now, consider the following method:
public void method() {
int [] numbers = { 1, 2, 3 };
int sum = numbers[0] + numbers[3];
}
Executing method()
will always yield an ArrayIndexOutOfBoundsException
since we are trying to read past the end of the array. Remember, an array index i
must be:
0 <= i <= (array.length - 1)
The method caller has no recourse because the caller cannot do anything meaningful in response to the error. This method, as well as method2()
, represents a runtime exception example. As I mentioned above, runtime exceptions indicate programmatic errors. Programmatic errors are normally unrecoverable bugs, so the proper recovery is to fix the error in the code.
As a rule of thumb, you should always catch a checked exception once you reach a point where your code can make a meaningful attempt at recovery. However, it is best not to catch runtime exceptions. Instead, you should allow runtime exceptions to bubble up to where you can see them.
If you do catch runtime exceptions, you risk inadvertently hiding an exception you would have otherwise detected and fixed. As a result, catching runtime exceptions complicates unit and regression testing. While testing, seeing a stack trace or allowing the test to catch and report runtime exceptions lets you quickly identify problems. Some programmers advocate catching and logging runtime exceptions, but I disagree because that makes you read through logs while you unit test your code. A unit test should indicate whether the test passed or failed without manual verification from a log. Let the unit test catch the exception, not the code being tested.
Catching runtime exceptions also leads to a worse problem: what exceptions do you catch, and when do you catch them? Runtime exceptions are undeclared, so how do you know what you should catch? How do you know there’s an exception to catch? Certainly you wouldn’t place try/catch blocks around every method call and array access you perform?