Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Exception Handling

Author: Ian Darwin
Published? true
FormatLanguage: WikiFormat

Problem:

Java has a well-defined exception handling mechanism, but it takes some time to learn to use it effectively without frustrating either users or tech support people.

Solution:

Learn about the Exception hierarchy. Learn about Dialogs and Toasts.

Discussion:

Java has had two categories of Exceptions (actually of its parent, Throwable) from the beginning, checked and unchecked. In Java Standard Edition the intention was apparently to force the programmer to face the fact that, while certain things could be detected at compile time, others could not. For example, if you were installing a desktop application on a large number of PC's, there would certainly be some on which the disk was almost full, and trying to save data could fail, and others on which some file the application depended upon would go missing, not due to programmer error but to user error, filesystem happenstance, gerbils chewing on the cables, or whatever. So the category of IOException was created as a "checked exception", meaning that the programmer would have to check for it, either by having a try-catch clause inside the file-using method or by having a throws clause on the method definition. The general rule, which all well-trained Java developers memorize, is the following:

Exception, and all of its subclasses other than RuntimeException or any of its subclasses, is checked. All else is unchecked.

So that means that Error and all of its subclasses are unchecked. If you get a VMError, for example, it means there's a bug in the runtime. Nothing you can do about this as an application programmer. "Nothing here, move along." And RuntimeException subclasses include things like the excessively-long-named ArrayIndexOutOfBoundsException - this and friends are unchecked because it is your responsibility to catch them at development time - using Unit Testing.

So here is a diagram of the Throwable hierarchy:

Where to Catch Exceptions

The early (over)use of checked exceptions lead a lot of early Java developers to write code that was sprinkled with try/catch blocks, partly because the use of the "throws" clause was not emphasized early enough in some training and books. As Java itself has moved more to Enterprise work and newer frameworks such as Hibernate and Spring have come along emphasizing use of unchecked exceptions, this early problem has been corrected. It is now generally accepted that you want to catch exceptions as close to the user as possible. Code that is meant for re-use - in libraries or even in multiple applications - should not try to do error handling. What it can do is what's called "exception translation" - that is, turning a technology-specific (and usually checked) Exception into a generic, unchecked exception. The basic pattern is:

public String readTheFile(String f) {
        BufferedReader is = null; 
        try {   
                is = new BufferedReader(new FileReader(f));
                String line = is.readLine();
                return line;
        } catch (FileNotFoundException fnf) {
                throw new RuntimeException("Could not open file " + f, fnf);
        } catch (IOException ex) {
                throw new RuntimeException("Could not read file " + f, ex);
        } finally {     
                if (is != null) {
                        try {
                                is.close();
                        } catch(IOException grr) {
                                throw new RuntimeException("Error on close of " + f, grr);                      
                        }               
                }
        }                       
}  

Note how the use of checked exceptions clutters even this code: it is virtually impossible for the is.close() to fail, but since you want to have it in a finally block (to ensure it gets tried if the file was opened but then something went wrong), you have to have an additional try/catch around it. SO: Checked Exceptions are generally a bad thing, should be avoided in new APIs, and should be paved over with unchecked exceptions when using code that requires them.

There is an opposing view, espoused by the official Oracle site and others. Al Sutton points out that "Checked exceptions exist to force developers to acknowledge that an error condition can occur and that they have thought about how they want to deal with it. In many cases there may be little that can be done beyond logging and recovery, but it is still an acknowledgment by the developer that they have considered what should happen with type of error. The example shown ... stops callers of the method [from] differentiating between when a file doesn't exist (and thus may need to be re-fetched), and when there is a problem reading the file (and thus the file exists but is unreadable), which are two different types error conditions." Yet again, the Sun/Oracle JDK documentation suggest wrapping exceptions (without regard for checked or unchecked): "One reason that a throwable may have a cause is that the class that throws it is built atop a lower layered abstraction, and an operation on the upper layer fails due to a failure in the lower layer. It would be bad design to let the throwable thrown by the lower layer propagate outward, as it is generally unrelated to the abstraction provided by the upper layer. Further, doing so would tie the API of the upper layer to the details of its implementation, assuming the lower layer's exception was a checked exception. Throwing a "wrapped exception" (i.e., an exception containing a cause) allows the upper layer to communicate the details of the failure to its caller without incurring either of these shortcomings. It preserves the flexibility to change the implementation of the upper layer without changing its API (in particular, the set of exceptions thrown by its methods)."

Android, wishing to be faithful to the Java API, has a number of these checked exceptions (including the ones shown in the example), so they should be treated the same way.

What to do with Exceptions

Exceptions should almost always be reported. When I see code that catches exceptions and does nothing at all about them, I despair. The point of all normal exceptions is to indicate, as the name implies, an exceptional condition. Since on an Android device there is no system administrator or console operator, exceptional conditions need to be reported to the user.

You should think about whether to report exceptions by a Dialog or a Toast. The exception handling situation is different than on a desktop computer. The user may be driving a car or operating other machinery, interacting with people, etc., so you should not assume you have their full attention. Remember that a Toast will only appear on screen for a few seconds. If there is something the user needs to do to correct the problem, you should use a dialog. I know that most examples, even in this Cookbook, use a Toast, often only because it's less coding. Toasts simply pop up and then obliviate. Dialogs require the user to acknowledge an exceptional condition, and either do, or give the app permission to do, something that might cost money (such as turning on internet access in order to run an application that needs to download map tiles). Use Toasts to present information; use Dialogs to obtain confirmation.

alsutton 2010-07-13 03:50:45.239 One last point; Oracle have already made the position on unchecked exceptions pretty clear, and it's not what this article advocates; http://download.oracle.com/docs/cd/E17409_01/javase/tutorial/essential/exceptions/runtime.html
alsutton 2010-07-13 03:17:03.066 [Please remove previous comment, this is a more succinct answer] Checked exceptions exist to force developers to acknowledge that an error condition can occur and that they have thought about how they want to deal with it. In many cases there may be little that can be done beyond logging and recovery, but it is still an acknowledgment by the developer that they have considered what should happen with type of error. The example shown is a particularly bad one as it stops callers of the method differentiating between when a file doesn't exist (and thus may need to be re-fetched), and when there is a problem reading the file (and thus the file exists but is unreadable), which are two different types error conditions.