Exception Handling in Java and C#

 

Howard Gilbert
3 May, 2003

Examples and Motivation

Java and the .NET Framework may represent a new program execution environment, but they do not change the principles of software engineering. The basic elements of modularity, strong typing, and interface design have new styles, but no new substance. The same may be said of Exception handling. Java and C# may introduce new stylistic elements, but the elements of proper error handling have not changed in twenty-five years.

Exceptions have been part the hardware design of every CPU. Necessarily, exception handling is a feature of every operating system and most computer languages. In modern processors an exception generates a forced "subroutine call" from the statement that caused the error to a handler established by the program, runtime environment, or system. At the hardware level an exception handler can return to the instruction that generated the problem (as happens after a page fault is resolved). This is a dangerous facility to give to an application program, because the code can easily loop raising the exception, handling it, and then returning back to a statement that raises the exception again. Java and C# have adopted a safer approach, where an exception always "abruptly terminates" a statement or sequence of statements in a block and exits to an outer block of code.

"Anything that can go wrong will go wrong." This basic principle of system design guides our view of exception handling. All programs have bugs. If the program is perfect, there can be bugs in components it calls. If the library is perfect, there is an occasional hardware error or network problem. If the one computer is perfect, there can be failures in databases and Web Services accessed on other machines. If all the network servers are perfect, there can still be power failures. If the environment is reliable, human beings can always trip over a power cord. Professional programmers may add special error handling for the problems they expect to happen most frequently, but they provide comprehensive error recovery across the entire body of the program for all the other problems that nobody can anticipate. The first principle of exception handling is simply this: Exceptions are not exceptional. Exceptions are the rule, not the exception.

The first launch of the Ariane 5 rocket failed 40 seconds into the flight with a loss of a half billion dollars. The problem was tracked down to an exception thrown by some code that had originally been written for the smaller Ariane 4. This particular routine wasn't really needed during flight, but it had been left running because it was difficult to turn off. During this first flight of a bigger rocket, the code calculated a bigger value than it had previously generated. It then tried to store the number in a variable declared to be of type "short". This time the number was too large for a 15 bit integer, producing what is called an "overflow" condition. Integer overflow checking can be disabled on any CPU. Java always disables it. C# disables it by default, but will enable it in any checked block. The Ariane program was written in a language that enabled overflow exceptions by default, but the program didn't have a handler to catch them.

An empty exception handler would have been fine. Since the code wasn't doing anything useful itself, the exception handler did not have to do anything useful. It just had to stop the propagation of the exception before it damaged something important. Unfortunately, without any exception handler the error propagated up to the operating system which terminated "the program". In this case, the program was the guidance program for the rocket. There were other computers, but they all had the same code and they all crashed. Without any guidance, the rocket had to be destroyed.

Ordinary programmers may not build anything that will crash quite as spectacularly. A casual application program running from the command line will terminate at the first error. The programmer can correct the error and rerun the program. This is not an unreasonable development cycle, but it also isn't an example of professional program packaging.

An online system, such as a Web application or Web Service, cannot afford to fail at the first exception. If it is providing a useful service to hundreds or thousands of other users, exception handling should be designed to localize the damage and minimize the inconvenience.

Proper error management is typically overlooked because it is expensive, complex, and thankless. When a program runs correctly, the flow of execution runs through the main path of the original program design. When anything that can go wrong does go wrong, then the program branches off in many less well traveled directions. There is one normal path, but hundreds of possible error scenarios. The code in some parts of some operating systems is 95% error handling and only 5% normal processing. Amateur programmers want to avoid the work, and businesses want to avoid the expense.

There are times when a program can reflect creativity and nuance. Exception handling isn't one of them. Error handling should be routine, methodical, reflex, and fascist. This is one of the complex programming problems, like synchronizing access to data in a multithreaded environment, that you simply cannot afford to make up as you go along. If you stop to invent a new way of doing it every time you have the need, then you will almost certainly make an error. If you apply exactly the same error management framework throughout all programs, then you have a reasonable chance of success.

Error Handling Models

Error handling in many operating systems is regarded as a mode. As a program executes, routines add error handling elements to the call stack and remove them when the routine returns. When an error occurs, the application enters an error handling environment and control passes to the most recently added error handler on the stack. When that error handler is done, control passes to the next most recently added element. If at any point an error handling routine can resolve the error and propose a point where normal program execution can resume, then the system can exit from the error handling mode.

Java and C# don't have real "error handling" in this sense. Instead, they have a throw statement that passes control to a matching catch clause. Throw is defined by the language as one of a family of jump statements including break and return. A break jumps to the end of a block in the same method. A return jumps to the next statement in the calling method. A throw, however, can jump to a catch clause in the current method, or in the calling method, or even farther back in the call stack.

These new languages don't define an error handling mode. The throw statement jumps to a catch clause. It has no further effect. In order for any catch clause to pass control to the next catch on the stack, it must end by issuing a new throw statement.

There is no particular reason to believe that an error can be resolved by the first error handling routine that is entered off the stack. The old model of an error handling mode reflected an empirical observation that an error generally has to run through several error handlers before you find a point of recovery. You can certainly accomplish the same thing in Java or C#, but you have to add your own code to chain from throw to throw.

So while the old systems provided a fairly clear error handling model for all programs, Java and C# require the program designers to develop their own error handling methodology. That is the motivation behind this paper. Java and C# have all the elements needed to develop an error handling methodology, but try-catch is just a statement.

The modern object oriented languages provide syntax to define classes, fields, methods, and blocks. The language scope, however, ends with the definition of one class. Error handling, on the other hand, involves the execution of a sequence of catch clauses that run in a particular order but are each defined in different classes, sometimes in different packages. The sequence of transfers from catch clause to catch clause is determined at runtime from the stack. The Java or C# compilers see no relationship between the catch clauses in different source files. That is why error handling has to be a methodology imposed on top of Java or C# by convention since it is not a direct part of the language.

Java (but not C#) has the convention that a method declares all the user defined exceptions that it can throw. This tends to force a program that calls the method to provide at least superficial explicit error handling for the declared exceptions. The problem is that new programmers can be left with the impression that the required error handling for explicitly named exceptions is sufficient. Software Engineering tells us that everything that can go wrong will go wrong, so a proper program design must anticipate all the undeclared exceptions that even Java allows every program to throw implicitly.

In Java all exceptions are subclasses of java.lang.Throwable. In the .NET Framework, they are all descended from System.Exception. In both Java and C# the exception is signaled by a throw statement that transfers control to a matching catch clause of a logically enclosing try block.

The processing of a throw statement in the Java language specification occupies several very dense pages of technical vocabulary. The exact language need not be reproduced here. However, there is a problem with the Java specification that needs to be confronted before anything else.

A statement or block of statements is said to "abruptly complete" if the normal execution of statements one after another is disrupted by a break or continue statement (transfer control to the end of a loop), a return statement (transfer control back to the statement that called the current method), or a [C# only] goto statement.

A throw statement (or an exception implicitly generated by the JVM or CLR)  also abruptly completes a sequence of statements or method call. It transfers control directly to an enclosing catch clause in the same routine or in one of the calling routines on the stack. Many Java exception classes have the word "Error" in their class name, and there is even a large subclass of throwable objects that inherit from the Error class. However, the operation of throw and catch are simply a flow of control. There are uses for this type of jump that are not related to "errors" per se.

In the US, the terms "throw" and "catch" suggest baseball, and that is not a bad analogy. In one sense, each throw ends with the next catch. However, there are times when a fielder will relay the ball toward home plate through the shortstop. There is a view that the throw starts with the fielder and ends at the plate, with the help of the shortstop. In this same spirit, error handling goes through a number of catch clauses each of which may rethrow either the original exception object or a derived exception object that wraps the original exception. Error handling looks at the whole relay.

The catch clause executes and ends in one of five ways:

  • The statements in the catch clause can simply fall through to the end of the try block. From the point of view of Java, the throw ended at the catch and the catch clause ended normally (not "abruptly") so execution proceeds normally. From a broader cross-language perspective, the program exits from "error handling" and resumes normal execution.
  • Control may leave the catch clause due to the execution of one of the non-throw jump statements (return, break, continue). From the Java language point of view, the catch clause "terminated abruptly", but not with a throw statement. In broader terms, however, this is a normal termination of the error handling just as if the catch clause fell through to its end. The program can be said to exit "error handling" and resume normal execution.
  • The catch clause can rethrow the original exception object. Execution now jumps to the innermost try block enclosing this catch clause that contains a parameter whose type is compatible with the original exception class. Error handling continues.
  • The catch clause can create and throw a new throwable object derived from (and linked to) the original exception. Execution now jumps to the innermost try block enclosing this catch clause that contains a parameter whose type is compatible with the new derived exception class. Error handling continues.
  • A catch clause can terminate in error by accidentally generating an unrelated exception. This produces a secondary error (an error in the error handling routine). Unfortunately, the original error is not linked to the new error and is, therefore, lost to any further processing. For this reason, in production code any non-trivial catch clause should contain at least one nested try-catch block so that exceptions generated during exception handling are handled, but do not block further processing of the original (and probably more important) error.

An error handling methodology selects any of these alternatives under the right circumstances. When should a catch clause rethrow the same exception object? When should it derive a new object, and what class should it use? How should a program handle errors inside an error handler? A methodology specifies the answers to such questions.

Between the point of the throw statement and the first matching catch, the runtime pauses as it moves up the call stack each time it encounters a try block with a finally clause. It executes all the statements in the finally clause, then resumes looking through the stack for a catch to match the pending throw.

Finally doesn't just trap exceptions. It also traps any of the jump statements (break, continue, return, or goto) that would exit from a try block that contains a finally clause. For that matter, the finally clause also executes when the try block ends normally and falls through after executing the last statement.

Java has a problem that C# corrects. Java allows a finally block to include one of the jump statements. If the finally block is entered from a throw statement, then execution of a break, continue, or return in the finally block overrides the throw statement and aborts error handling. This is a design problem because the finally clause cannot actually handle the error since it doesn't have access to the exception object and cannot even determine that it has been entered as a result of a throw rather than from some other cause. C# solves this problem by prohibiting any jump statement in a finally block that would transfer control outside the block.

A try statement can have both catch and finally clauses. The language states how each clause works, but doesn't give much direction as to their proper use. Since the catch statement is clearly part of error handling, and the finally statement (in Java but not C#) can trap and abort error handling, programmers are often misled into believing that finally can handle errors. The problem is that finally did not know that there was an error, did not have access to the exception object or any of the objects referenced by it. It can't log, diagnose, or correct anything. Furthermore, it jumps to the same location when entered normally or as a result of an error. Thus finally is never part of error handling, although it may be used to achieve unconditional error cancellation.

This behavior of a finally clause is not limited to exception handling. Suppose the try loop executes a "return 25" statement. This statement wants to return to the caller of the method an pass back a value of 25. However, if the finally clause for the try block executes a break statement, then the attempt to return from the function is now aborted. The value of 25 is dropped, and the routine that was supposed to return now continues execution at the end of some enclosing loop. Plausibly this could undermine the intent of the original statement that really did think that the function was done and really did want to return a value to the caller.

The finally clause is entered when a block of code runs to its end, or when a statement tries to exit the block and jump to an outer block, or when an exception occurs. It temporarily intercepts the originally intended transfer of control. The problem is that it doesn't know which of any of these conditions it intercepted. It doesn't know where the code is jumping from, where it is jumping to, or what type of statement caused the jump. If it is a return it doesn't have access to the return code. If it is a throw it doesn't have access to the exception object.

For a finally clause to blindly drop all current error state information, override the originally intended transfer, and unconditionally jump to some new location in the program, it must have discovered something so overwhelmingly important that it takes precedence over any other possible program logic. There may be a few cases where this can occur, but they would be very unusual. They are strange enough to propose a "best practice" that is nowhere mentioned in the Java language manuals:

A Java finally clause should almost never contain a break, continue, or return statement and should be careful to only throw exceptions that reflect a preemptively significant problem. These statements blindly, absolutely, and unconditionally interfere with whatever the program was doing before the clause was entered. Normally the finally clause should perform its intended functions and then end normally, allowing the system to resume what it was doing.

A finally clause that obeys this rule is slightly different than the raw finally clause defined in the manual. This "well behaved" finally clause will have no effect on exception handling. If the finally clause is entered because of an exception, then when the clause is done the exception will still be pending and will be passed on to the catch and finally clauses at the end of the next outermost try block.

A C# programmer doesn't have to worry because the C# language prohibits the use of break, continue, or return to exit from a finally clause. All C# finally clauses have to be well behaved.

With this clarification, we see that the try-catch and the try-finally do two entirely different things. The catch block handles exceptions. The finally clause intercepts any attempt to exit a block and is most commonly used to clean up local resources.

Lost somewhere in the detailed description of the try, catch, and finally clauses in Java and C# is a much simpler error handling concept that was central to the older systems. The runtime enters a sort of "error handling state" when the throw statement is executed. It stays in that state as long as the throw is still pending. The error can be trapped by the innermost matching catch clause in an enclosing try block. This error handler ends in one of two ways:

  • If the block rethrows the original error or throws a new derived error or generates an unplanned exception because of a program error in its own code, then the system remains in an error state and the original or superceding error condition percolates up to the next innermost try block on the stack.
  • If the block ends without throwing any type of exception and either falls through after the last statement or executes a  return, break, continue, or other jump to code outside the try block, then the system exits error processing and resumes normal execution. Traditional programming terminology calls this a retry from the error handler.

Error Handling Phases

The processing of the error is divided into three phases.

  1. The first phase allocates and initializes the "throwable" object and throws it. The most basic throwable objects just contain a message string. The Java runtime attaches a stack trace to throwable objects that points to the statement that created them. Derived classes may contain other properties that are filled in when the object is created.
  2. The second phase, which may be repeated, consists of catching the object, classifying the exception, evaluating the scope and consequences of the problem, and then either rethrowing the original exception or throwing a new derived exception chained to the original exception.
  3. In the final phase, the object is caught and examined as before. However, this analysis yields a plausible "solution" where the program can exit from error handling and resume normal execution. The catch clause doesn't throw any further exceptions. It either falls through to the end of its try block, or it jumps with a return, break, or continue. Error handling is now done, at least for this error.

If a statement can detect an unusual condition and handle it itself, then there is no need for exceptions at all. The nature of the throw-catch model is that Phase 1 processing (the creation of the object and first throw) knows what went wrong but doesn't know what to do about it. It has the most information about the error, but no clear view of the overall application structure. This is particularly true of small utility routines that can be called from anywhere.

The throw then passes control to an outer block that matches the general category of thrown objects, although it can match everything of type Exception or even Throwable. Although you can catch and rethrow an exception "on spec" (expecting to add specific error processing later), in most cases a try-catch clause is supposed to be inserted into the program structure at a point where the program logic suggests a possible error processing and recovery strategy.

In practice, many new Java programmers wrap some statements in a try-catch clause simply to get rid of compiler complaints that a method must either declare or catch exceptions. This language feature was introduced for a purpose, not to generate automatic meaningless boilerplate code.

A catch statement should, in general, be introduced into the code at logical points when it is possible to describe in a simple statement what the code is trying to do and what is the alternative strategy to use when something goes wrong. The exception says what went wrong, which may or may not be important to the catch logic.

For example, a program is trying to open an XML configuration file. The disk may not be found, or the file may not be found, or the file may be empty, or the file may not contain valid XML text. Each error condition is associated with a different exception plausibly thrown by either the java.io.* package or the XML parser. No matter which of these errors occurs, the program is not going to be able to read valid configuration data from the file. Its options are then to give up and end the program, continue processing using defaults instead of the contents of the file, or to try and obtain the same configuration information from another source.

The try-catch logic would then surround the "get configuration data from XML file" block of code. The catch clause might just match all Exception or even all Throwable objects, since the recovery strategy is based on the calling program logic and not the type of error.

The remaining issue is how and where to report the error so it can be corrected. If the error requires attention by the system administrator or application programmer, then the best strategy is to trap it at the first possible movement and to report the useful information about the error in a system log file. If the error was probably triggered by user input, then the best strategy is to trap it at a very high level in the application, where the catch clause or retry logic has access to the response or form object that provides an interface to the user.

Eventually the error must be retried or the rocket blows up. Note that a retry doesn't mean that the program will run successfully. After the retry, the code may return an error message, or a null reference, or some type of code. The program must still deal with the consequences of the exception, but it does so outside the context of exception handling and catch clauses.

The three phases are an abstraction. They represent tasks and not necessarily separate units of code. In some cases the problem may be logged, the consequences determined, and a retry decided all by a single block of code. Even then, it is likely that the three steps will be taken in this particular order.

Boundary Between Components

Some error handling conditions cross a boundary between components. Four notable examples come to mind:

  • Some errors are detected by the JVM itself. Since no program code is involved in the allocation and initialization of the Throwable error, the first chance code has to perform Phase 1 processing is in the first catch clause.
  • Some errors are generated by library packages such as JDBC or JNDI. An application programmer can reasonably regard distributed Sun libraries as elements of the Java language itself, so there is no reason to treat SQLException differently from NullPointerException.
  • Some errors are generated by specific routines written to break a large application up into modular components. The "read configuration parameters from XML file" routine would be an example. Unlike library routines, this code knows exactly what it is doing and the larger application program logic because it is probably only called from one place in one program.
  • Errors can, however, be generated by general library routines obtained from a vendor or written by some other group in your own organization. These routines perform a specific function that could be used in a broad variety of applications. If such a routine throws exceptions, then documentation about the type of exception and the reason why it is generated should be part of the general program documentation. A programmer looking to call a library routine should be told the arguments to pass, the type of the return value, if any, and the type and meaning of exceptions thrown.

There are to extreme cases: absolute trust and absolute distrust.

Trust: The Java library routines have been carefully written and are aggressively tested. Although there is always some chance of a mistake, most programs will assume that any exception thrown by the Java library is caused by an error in their own program. They will not, therefore, distinguish between an error generated by the JVM and one generated by a collections class.

Distrust: Whenever a J2EE container calls an EJB, or a Servlet container calls a Servlet or JSP page, or Struts calls an Action Bean, the caller has a plausible reason to distrust the user code that is about to execute. The calling container is carefully written logic that should plausibly be stable. The bean or servlet is user written code that could still be under development. In these cases it is common for the caller to wrap the call to user junk with a try-catch clause that catches everything. It doesn't really matter how or why the user written junk screws up, it isn't a problem in the Web Server logic.

Responsibilities

There is an important difference between a NullPointerException generated by a bug in your code and an edu.yale.its.myapp.ConfigurationParameterError exception that an application program defines, initializes, and throws itself. An exception generated by the JVM or Java library will not have no message written or logged when it is created. Therefore, diagnostic information can only be captured in a catch clause. On the other hand, a user defined Exception class is constructed by the application program class. Error messages can be written to standard output or to the log at the point where the error is detected, before the first throw.

In general, diagnostic information is most easily generated at soon as possible, from the innermost code that detects it. This means that code that creates a new Exception object generates its own error messages, and the first catch clause that processes an exception thrown by the Java system or by a called external component generates diagnostic messages on that error.

There is a problem, however, if it is not clear at the point where the error was detected as to how the error should be reported. a FileNotFound error might be reported to the System Administrator if the file in question is a configuration file that was supposed to have been provided during application installation. However, the same exception might be reported back to the end user if the file name was entered into a form field. So errors trapped by general purpose routines may not be logged until the error percolates far enough up the calling stack so that the appropriate form of error reporting can be determined.

If the problem percolates all the way to the top, the program ends and the rocket blows up. If this is not a good idea, some program must decide that that original error has been completely processed and that the results that would have been produced by the correct execution of the code in error can be replaced by some substitute. The result of a retry might be an error message, or a null string, an empty Hashtable, and so on. The program may then report an error or simply show that no meaningful data could be reported.

Report

There are four plausible audiences that should be considered when generating or processing an exception

  • The End User - The person who generated the request may need a clear statement that something went wrong and that data or operations are incomplete. This is not the place for technical information. First, make it clear that the reason why there is no data is that something went wrong and not that there really is nothing there. Then provide a suggestion about something the user can understand and do, like "Try again later".
  • The Application Administrator - Problems can arise from the way an application is installed or configured. There may be a bad parameter in some XML configuration file. The connection information for the database may be wrong, or the database may not be correctly loaded. A package or property file could be missing from the CLASSPATH. Information reported to the Application Administrator has to be in terms that make sense to someone who has only read the installation documentation for the package. You want to say something like "The <maxworker> tag in the configuration.xml file must contain an integer from 1 to 10, it currently contains the string 'What is this?'". This is not the place to put a program stack trace.
  • The Programmer - Errors in actual code must be fixed by a programmer. Assuming that you have filtered out all the user and configuration errors, the remaining null pointer and string range exceptions are probably program errors. This is the place where you want a stack trace, or at least the part of the trace that applies to the actual application code.
  • The Catch Statement - The throw statement is an implied communication between the code that detected the error and created the Throwable object and the programs that are going to catch the error. The object itself and all the properties stored in it are the parameters of this communication. The object class should be designed to contain all the information needed to clean up or retry in the upper layers.

The most common mistake is to fail to distinguish between the various audiences. The most visible result is some poor end user looking in his browser at an error message and a program stack dump through a hundred different methods involving the Web Server, application logic, and support routines like JDBC. A well designed application will generate a response to the browser and messages to separate Administrator and Programmer logs.

Clean Up

A common C programming mistake was to leave the address of dynamically allocated storage in a variable after the storage itself had been released by a call to free(). Subsequent use of the variable could generate an exception or, if the same address had been allocated for some other use, could improperly access some other data through the wrong structure.

It is not possible to solve this problem without disallowing the free() operation entirely. The Ada programming language did this, but it was not object oriented and had more extensive and complex language features for allocating dynamic arrays and structures on the stack as local variables of subroutines. Without some way to free storage, an object oriented language like Java or C# would quickly run out of virtual memory.

The solution is Garbage Collection. After an object is allocated, a reference to this object can be stored in a static or local variable, or in some table or object accessible through a static or local variable. The system tracks such references. Periodically, when memory is getting full, the system identifies all objects that are still accessible through some valid reference variable. The other objects are finalized and their storage is freed up.

An open file, a network connection, a JNDI context, a JDBC result set, and hundreds of other objects reserve file descriptors, buffers, and a lot of memory. Not only do they use up memory in the application, they also use handles or file descriptors in the OS, and through open sessions they may allocate buffers, transactions, and resources on remote database or other servers. Eventually Garbage collection may get around to freeing these resources up, but there is no guarantee that it will happen soon.

It was always true in every language and every operating system that whatever is opened must be closed, whatever is allocated must be freed, whatever is attached must be detached. This requirement applies not just to the correct execution of the program, but to every unusual flow of execution due to exceptions.

try and finally

Java and C# provide probably the best language construct ever designed to ensure proper cleanup. It is the try-finally compound statement. The try keyword introduces a main block of code. Once the program enters the try part of the statement, it cannot leave the statement without executing the finally clause. Of course, when execution gets to the end of the try block it falls through to the finally clause. However, the finally clause also intercepts any attempt to exit the block through a jump statement (return, break, continue, or goto[C#]). It also traps an exit from the block forced when an exception is thrown or percolates from one handler to the next.

The try-catch and try-finally are two entirely different constructs with different flows and uses. A catch clause is only entered when a matching exception is thrown. It handles the exception and it can optionally terminate error processing and retry to normal program flow simply by ending normally. A finally clause is always executed whether the try block ends normally, ends with a jump statement, ends with a return statement, or ends by throwing an exception. A finally block cannot meaningfully handle an exception because it has no access to the exception object, no way of even knowing that the exception has occurred, and if it is "well behaved" (with no return, break, continue, or throw statements) it will end with the same error condition it was entered with.

As soon as a resource has been allocated that needs to be cleaned up, all use of that resource should be wrapped in a try block and the cleanup logic for the resource should be invoked in the finally block. The resource is represented by an object and the cleanup is performed by calling a method named "close()" or "dispose()" on the object.

Once an object has been closed, it is a programming error to subsequently try and use it for normal purposes. The simplest way to avoid this error is to make sure there are no references to the object after it has been closed. If the only reference to the object is a control variable that goes out of scope when the block terminates, then the compiler will detect any attempts by the program to use the object outside that scope. Otherwise, the control variable has to be set to null after the object has been closed any any program error that subsequently attempts to use the old object will generate a null pointer exception.

The following basic program structure would be the same in both Java and C#. The Java syntax is used. C# would be the same program, but with different class names for the streams and exceptions.

 { OutputStream out = null;
try {
out = xxx.getOutputStream();

// process file ...

} finally {
if (out!=null)
try {out.close();}
catch (IOException ioe) {;}
out=null; // see below
}
} // variable "out" is no longer in scope

An outer { } block surrounds the try-finally to limit the scope of the control variable "out", which has to be available in both the try and finally clauses but should not be available outside this code.

IDisposable and using [C#, but an example to Java programmers]

When a Java OutputStream is closed(), the runtime flushes any buffered data and writes it to the disk or socket. At that point an error can be detected and an exception is thrown to indicate that the file was not written successfully. Other close() methods in other classes perform similar functions and throw exceptions, although the nature of the exception varies from class to class.

With the benefit of a few more years of design, Microsoft realized in its .NET Framework that there are two distinct operations here. The close() method is a normal operation like write(). It belongs in the try { } clause for the object. It can fail. Separately, there is an operation that simply releases the resource of the object. It may finish off any pending business, but it cannot throw an exception because it simply ignores all problems. In the .NET Framework this method is called dispose() and the objects that present this method are said to implement the IDisposable interface.

Java, unfortunately, has no such clear distinctions. There is no universal dispose() method, and no interface. The close() method, when it is supported, has a different signature (because it throws different exceptions) for different classes. There is not even a guarantee that the cleanup method will be called close().

The C# language now adds a statement to exploit the conventions of the framework class. Just as the Java catch statement is defined for object that inherit from Throwable, the C# using statement is defined for all objects that implement the IDisposable interface. The using statement encapsulates the try-finally block and the close()/dispose() method call:

 using (Stream out = xxx.getOutputStream()) {

// process file ...
}

The using keyword is followed with parentheses that enclose either the declaration and initial assignment of a variable of a class implementing IDisposable or an expression yielding an object of the type IDisposable. It is then followed by a block of code. Implicitly, this is a try block. Implicitly, there is an automatically generated finally clause that calls the dispose() method on the IDisposable object. In this particular case, the content of the implied finally clause is:

	finally { if (out!=null) out.dispose()}

This paper will say something about the forms of the using statement after all the other responsibilities of cleanup code have been addressed.

Commit and Rollback

When a program tries to write() data, or close() an output file, or insert rows into a database table, or define entries in a directory, then something can go wrong. There may be a disk I/O error, or a network error for a server resource, or a security or data integrity violation.

Databases have this problem all the time, and they provide both the terminology to describe it and the algorithms to address it. Databases encapsulate incomplete persistent data within a framework called "transactional integrity". If everything ends normally and the data is complete, the transaction can commit the changes to disk. If something goes wrong and the data is incomplete or in error, then the changes can be backed out with a rollback operation.

There is a commit-rollback concept that applies to each case beyond the database environment. For an ordinary file on disk, a rollback might be achieved by deleting the file that contains the incomplete or bad data. For an HTTP request, a rollback might discard the reply buffer and generate a 400 or 500 series error code to the browser.

Since you rollback after an error, an obvious convention would be to expect to put rollback logic in a catch clause. However, there are lots of ways that a try block can end abruptly, with a return or by throwing some unexpected Throwable class. The commit-rollback decision is part of cleanup, and it should therefore be done in the finally block.

In most cases we can simplify things a bit more by suggesting that the "commit" function, which could be a real database commit, or the close() of an output file, or the Response.End() of ASP.NET, be performed inside the try block as the last part of normal processing. Such an operation may have the secondary effect of releasing resources, but no serious error will be generated if a second attempt is made to release the same resources in the finally block.

As a result, the cleanup logic in the finally block can check to see if the commit operation has already been performed. If it hasn't, then the try block ended abruptly and the proper response is to perform a rollback as well as releasing the resources.

Reconsider using

There are two versions of the C# using statement. It is now possible to explain why the simpler version of the using statement shown previously, and used in all the textbook examples of C# programming, it the wrong one to use. In the simple version,

    using (SomeDisposable i = new SomeDisposable()) ...

the using statement is followed by an initializing declaration of a reference variable to a class that implements IDisposable. The syntactic scope of that control variable is limited to the block controlled by the using statement and the cleanup is implied. Within the block the control variable is a constant (read-only).

This construct doesn't provide all the flexibility needed for professional programming:

  • For every object that holds meaningful resources, it will be necessary to pay serious attention to the exceptions thrown by the creation of the object. In any real code, opening a file for output will generate "file/path not found" or "directory is read only" errors. URLs can be malformed or the server may not respond. Databases may refuse a connection or the query SQL may be invalid. This form of the using statement requires a simple declaration with initialization statement, and you cannot embed a catch clause for exceptions that the initialization expression may throw. To handle such exceptions, you have to embed the using statement inside an enclosing try-catch which then traps exceptions in the entire block instead of just the initialization statement. That is typically not a well structured design.
  • Within the block the resource variable is read-only. You cannot defer initialization until you are in the block, nor can you set the controlling reference variable to null after closing the object.
  • Normal use of any of these objects will also throw exceptions. A file will generate I/O errors, a session is subject to network errors, and a database will generate various SQL errors. If these exceptions were to propagate to to a catch clause outside the using statement, the automatic dispose() call would prevent any retry that recovered and continued the use of the resource. So the program requires a try-catch clause inside the using statement to handle all these errors.
  • The implied finally clause releases the resources, but there is no provision to add logic to address the commit-rollback question when this object represents persistent data. To add persistent data management, the block then requires a try-finally nested inside the using.

To recap, the simple using block in the textbook examples has now turned into a try-catch (for initialization errors) containing a using containing a try-finally (for rollback) containing a try-catch (for simple errors). So much for "simple". You may still want to code the using, but this has become complex enough to consider the second form.

In its alternate form, the using keyword is followed by an expression representing an object of a class that implements IDisposable. Put simply, it looks like this:

    SomeDisposable x = null;
try {x = new SomeDisposable());} catch ...
using (x) ...

Although the variable x is used in the using expression, in this form the using statement becomes associated with the object that x references instead of the variable x itself. The variable is, therefore, not constrained to be a constant. This makes it easier to design subsequent clauses that use, close, commit, or rollback the object.

Retry

Every error has some scope of damage. If you cannot read a file, then the content of the file is unavailable. If you cannot connect to the database, then the results of any query are unknown. This effects the parts of the program that depend on the data that could not be fetched or generated. It does not necessarily effect the entire program.

Some results may be optional. If so, then the program can retry to a statement that assumes the information was not available. In some cases the information is replaced by an error message. Thus a portal might generate an entire page but contain a message indicating that the local weather forecast is not available in the section where the weather data would ordinarily be. On the other hand, a problem fetching urgent announcements might result in a layout that assumes there are no urgent announcements rather than making the failure to fetch data look like it itself was an urgent announcement.

Retry has to occur at some level or the rocket blows up. However, the design of a retry strategy is driven by the structure of the program itself and not by the nature of the error. Anything that can go wrong will go wrong. The program design must assume that error of all sorts, some expected and some not anticipated, will occur in any stretch of code. You retry at the point where you have a plausible alternate action no matter what the individual problem may be.

There are various levels of damage. The closer the retry is to the code that failed, the more likely the damage will be slight. In increasing severity:

  • The error was unimportant. The processing or data was optional. The request can continue.
  • The request can continue and will produce partial results that may be useful.
  • The request cannot proceed, no useful data will be generated for this request, but other requests may succeed.
  • All requests through this session or object will fail. It must be discarded and a new session or object created.
  • The service is unavailable at this time, try again later.

Retry is determined in the catch clause. You cannot make the retry decision in a finally clause because if you enter the finally with an error condition you leave the finally clause with the same error or with a superceding error. Only a catch clause can clear the error state and return to normal processing.

This is a bit surprising because in every other error handing system or language the general rule is to first cleanup resources and then to decide if you retry out of the error state or percolate to the next innermost error handler. However, if a Java try block has both catch clauses and a finally clause, then the retry decision is made first in the catch clause, and then the cleanup is done in the finally clause after the retry decision has been made but before normal execution resumes.

The reason why this is not a problem is that the scope of resource management and error handling are slightly different. There is no reason to expect that a block will do both. It is plausible that a program will have only try-finally blocks to effect cleanup of resources, and try-catch blocks to handle errors and make retry decisions.

Programming Conventions

In the real world, Java programmers typically write the sequence of statements needed to accomplish a function. When they go to compile the block, some method calls are flagged because the the method is declared to throw exceptions for which there is no explicit handler. Then the programmer either encloses the problem statements in a try-catch block or redeclares his function to pass the Exception back to the caller. Although the code was just added to get rid of compiler error messages, this becomes what passes for error handling logic in the program documentation. Since the C# compiler doesn't complain about unhandled exceptions, .NET programs may have even less error recovery logic.

There are two parties to every exception. One set of conventions apply to the programs that create, initialize, and throw exception objects. A second set of conventions apply to programs that catch, diagnose, and possibly recover from exceptions.

The try-finally or using blocks act during error processing, but they are not really part of error management. Since the same code is executed during the normal exit from the block, these structures are part of resource management and not error management. Here the programming technique is obvious. You generate a try-finally or using block every time you create an object that implements IDisposable (or the abstract Java equivalent). The block wraps all use of the object and the explicit or implicit finally clause disposes of the object. The two nuances that this paper has pointed out is that there is a distinction between the normal close() and the dispose() methods, and that there may be a commit/rollback consideration.

The positioning of try-catch blocks is more subjective. Generally speaking, catch clauses that perform Phase 1 processing to gather information and report or log the error should be wrapped as tightly as possible around the error source. The catch clauses that perform Phase 3 processing and retry out of the error condition should be wrapped as loosely as possible around whole blocks of code and don't generally care about the individual information about the error or the exception object.

In order to make exception handling a reflex action, the safest approach is to build retry from the outside in. That is, first build a retry block around the outermost elements of your component's public methods. Now drill down from that point to any finer elements of structural detail that present more localized retry points.

At the same time, wrap diagnostic trace and reporting try-catch elements as tightly as possible around every point of probable failure. Every time you open a file, or connect to a database, or call an external component. If you don't have a valid error logging and reporting strategy yet, put the catch clause in, pass the exception object to a static routine that you define and will fill in later, and then rethrow the same or a derived exception object.

A Very Old Problem

Any operation on an object has documented exceptions. Java requires that these exceptions be explicitly listed in the declaration of the method in the class or interface. C# doesn't require explicit declaration, so the exceptions generated by each operation are part of the documentation.

Even without the documentation you can usually with a bit of thought figure out what the possible errors are. When opening a disk file using a character string name argument, the operation may fail because

  • the string contains characters invalid in a path name
  • the file is not found
  • the path is not found
  • access control restrictions prevent the operation
  • there was a physical I/O error on the disk

All the possible errors were enumerated decades ago, and they are handled in some fashion in every programming language. Java and C# simply provide a new style for an old problem. Each of these problems may be associated with a different exception class, or several may be combined in the same class and distinguished by the value of properties in the exception object.

The different sources of error may be important as you decide how to report the error. A problem with the character string path name could be reported back either to the end user or the application administrator depending on the source of the string. An access control problem may be fixed by changing the permissions on the directory or by running the program under a different userid. A physical problem on the disk may require new hardware.

However, the program recovery from the error probably doesn't care about the details of the error. The contents of this file cannot be read, or the file name cannot be used to save new data. Whatever the reason, the program's logical response depends on the importance of this file to correct operation and the presence of any fallback to accomplish the same result through a different path.

catch (everything)

After a sequence of catch clauses that match specific exception classes, a program can add one final catch statement that matches all remaining conditions. The Ada programming language had a "when others" syntax for the last exception handler. The C# language allows the final catch statement to specify no Exception class and match all remaining classes. Java lacks a special syntax, but a final clause of the form "catch (Throwable x)" will match everything else.

Java made a good faith effort to declare every exception that a method might throw. Even then, Java excluded all subclasses of Error or RuntimeException from explicit declaration. C# doesn't bother to declare thrown exception classes in the signature of a method, although the .NET Framework documentation tries to list all the Exception classes that a call might generate.

Exception classes indicate the specific type of error and contain property fields that hold information about the source of the error. This classification and error information is critical to the reporting phase where messages are logged. It may be important in any general problem determination logic as the error is classified for subsequent processing.

However, recovery occurs at the point when a program finds it convenient and logical to recover. Since anything that can go wrong will go wrong, and all exceptions from the RuntimeException and Error class can always be thrown from any Java method, the program structure surrounding a program retry is usually independent from the specific type of error. Obviously a program might have special retry logic for specific errors that might be anticipated, like a file not found when reading input. On the other hand, a NullPointerException is simply an unknown program bug in the middle of a block of code or in a method called by one of your statements. Once the special cases have been handled, the program has to provide generic logic for handling everything else. That is the purpose of a general catch clause.

One of the basic software engineering principles behind object oriented programming is called information hiding. A class exposes a set of public fields, properties, and methods. The only thing a program needs to know about the class in order to use it is this public information. The private fields, and the internal logic of the methods are part of the black box. The compilers and the Java or .NET runtime enforce access control. A program cannot generate references to fields that have been declared private. However, when a statement deep inside the internal logic of a method of another class throws an undeclared exception, such as a NullPointerException, then it would be a violation of all principles of information hiding to expect the calling program to know anything about the program logic around the point of error.

As Software Engineers, we need to adopt a methodology for program design that preserves the principle that external classes are opaque even when dealing with exceptions. This produces only two logical possibilities. First, a class should be written so that it cannot, as a consequence of its code structure, throw an undeclared exception. If this is not the case, then the caller must logically add one more implied exception to the declared method signature, the "everything else" exception, and it must code for this additional exception just as it codes for all the explicitly declared exception classes.

When writing a public method of a reusable public library class, a program should ensure that the method cannot throw any undeclared exceptions. The simplest way to do this is to wrap the entire body of the method in a try block that ends in a catch (everything) clause. Obviously there may be more specific catch clauses that catch and rethrow all the declared exceptions. The catch (everything) becomes a fallback to prevent internal programming bugs from passing private information improperly across the public specification declared by your program. Generally, all remaining bugs will be logged and then a derived throwable object that is part of the declared interface will be allocated and thrown as a substitute for all the unclassified program bug exceptions.

Most programs are not written to these high standards of software engineering. So in the real world, the calling program is generally required to assume that the methods it calls may throw undeclared exceptions. If the calling program wraps the method call in a try block with catch clauses for all the declared exceptions the method might throw, then it needs to add one more catch (everything) clause as if the "everything else" exception was an explicit part of the method's public specification. Essentially this is the "library method ended due to a program bug" condition, and the appropriate response depends on the specific program logic.

When a program generates an exception, the Java language specification says that the section of code ends abruptly. That is a very apt description. Since it did not complete successfully, we have no expectation that it produced whatever operation it was called to perform. If all resources allocated by the program were properly protected by finally or using clauses, then abrupt termination of a routine doesn't have to leak memory or leave anything dangling. Unlike the old FORTRAN or C programs that could damage arbitrary data using a bad array subscript or pointer, the modern Java and .NET languages do not allow improper access to raw memory. So there are no secondary effects. The only consequence when a method call ends abruptly is that it doesn't perform it function and return a value.

A program must have some confidence in the basic runtime environment. You cannot afford to wrap every statement in a try block. So the standard library routines distributed with the JVM, and the methods of the .NET Framework, can reasonably be regarded as part of the language itself. If one of these routines throws an exception, it is reasonable to assume that the source of the problem is an error in the parameters your program provided rather than an actual bug in the Sun or Microsoft library. Similarly, you may regard library routines written by you or by other members of your development group to be extensions of your own program rather than opaque components.

Therefore, the program design calling for tightly wrapped try blocks around method calls and catch (everything) clauses applies in practice to calls from your program to foreign untrusted components. Just exactly how much trust you are willing to allow and which library routines you are willing to trust depends on the specific logic and requirements of a particular application.

Report Once from the First Opportunity

When writing a component that others will use, a good programmer should catch all his own errors. The programmer understands the structure of his own code, critical data areas, and dependencies on other components. Since the program author will be expected to do basic problem determination and to fix bugs, he is best suited to log information and generate a stack dump.

However, when an exception is generated by a library routine or by some other component your program calls, you also have a secondary problem determination role. Assuming that the authors of the other routine did some testing before checking their code into the library, the error was probably precipitated by parameters your program passed or the sequence in which operations were performed. Absent any assurance from the component documentation that diagnostic information is logged someplace else, your program will have to gather and report what information it can about the error.

The old mainframe operating systems provided a unified framework for error reporting. The user running a program could provide a file into which error information would be written. Separately, the operating system provided separate system files. The system administrator would configure a set of error characteristics, and when these conditions were matched a program error would also be logged into the system files for subsequent analysis.

Once the system had saved one copy of the diagnostic information about an error, it did not need to get another thousand copies of the same information about the same error. Two facilities were provided. If we translate the first into Java or C# terms, the mainframe version of java.lang.Throwable or System.Exception had a boolean property that was initially set to false but subsequently set to true the first time that basic information about an error was logged to some file. This property was then used by subsequent catch clauses to avoid relogging the same information to the same set of files. The second feature, which applied only to the system files controlled by the system administrator, kept a digest of the information about the throwable object in system memory after an error had been logged. Subsequent errors that had identical information were not logged since it was assumed that they would only duplicate existing files.

Unfortunately, neither Java nor the .NET Framework provide this level of sophisticated exception management. Any large software system that defines its own exception classes and provides it own runtime subenvironment can still benefit from this example of good system design.

Localization and Scope

One program in a component calls a method in an object belonging to another component. The method ends abruptly and generates an exception. Assume that the component reporting the failure has cleaned up any resources and generated any appropriate log files. What the calling program needs to know is the responsibility for the fault and the scope and persistence of the error.

As the component signals the error back to the calling program through the Exception object, it can assert several different possibilities:

  • The error may be attributed to parameters provided by the caller of the method. The parameters may be syntactically invalid (malformed URL or path) or library routines may reject a request that was based entirely on the parameter passed (filename not found). The method could perform its operation with valid parameters.
  • The error may be attributed to the state of the object. Either the value of previously set properties or the history of previous requests (read past end of file, read from a closed object) prohibits the operation. Generally a new object must be initialized and the operation retried on that object.
  • The error appears to be related to static properties and will persist until the class is reloaded or statically reinitialized. Generally the application, container, or context must be recycled.
  • The error appears to be in the program logic of the called routine. It will remain until fixed by a programmer.
  • The operation is prohibited by security policy. It will remain until permissions are changed.
  • The operation failed because of a timeout or session failure with an external component (a database transaction, an LDAP context, an IMAP session). Creating a new object instance should establish a new session to perform the operation.
  • The operation failed because a connection could not be made with an external service (database, Web Service, EJB, etc.). The operation cannot be retried until the external server becomes available.

There is also a special secondary form of all of these cases. It occurs when a third component on which the called component is dependent throws an exception with one of these indicators. The called component may allocate a new derived exception that contains a reference to the original exception. In this format, when the called program reports the "static class property" problem it refers by proxy to a problem in the properties of the other class and not its own class properties. The effect is the same and the container or context will still have to be recycled.

In practice it may be impossible to describe the error with this level of precision. This is a goal that programmers should aspire to. One consequence is that a method should always test the validity of parameters passed to it as thoroughly as possible before using them, and it should generate a different set of exceptions for parameter problems than for internal errors.

Recover, Derive, or Rethrow

If a method receives an exception from a call to another method in the same class or in a related class that is part of the same component written and maintained by the same group of programmers, then it is entirely reasonable to rethrow exceptions that have either a global significance or are of a type defined in the component package. Therefore a portal component can always rethrow a PortalConfigurationFileSyntaxException.

If a method receives a low level exception, typically a RuntimeException or Error exception triggered by a programming error or a CLASSPATH problem, then it should always derive a new higher level exception to be passed back to the calling program. At the end of the stack this might be a ServletException, but somewhere in the middle it might be a PortalPackageMissingFromPath exception to reclassify the ever popular ClassDefNotFoundError.

Recovery occurs at a point when the program can proceed without the results that could not be obtained due to the abrupt termination of the code that threw the exception. This is mostly a structural feature of the design of the nested blocks in the program and not necessarily an attribute of the exception. For example, when the Web Server calls the Servlet's doGet() method, it wraps this call in a try block that catches and retries from all exceptions. No matter how badly the Servlet method screws up, this should not drag down the entire Web Server or even terminate a worker thread.

After a failure, a component recovers at the point when the abrupt termination of some particular operation either doesn't matter or has a plausible substitute action. This is driven more by the logic of the application and the service it is supposed to deliver rather than the cause of the error.

Common Helper Routines

At the throw

When a program raises an exception, it must first create a new throwable object. The constructor for the exception object class is called. Standard inherited constructors accept an error message and an optional link to a previous exception. Then the throw statement is executed.

Code that detects and throws the exception is best able to describe its source and identify information that will be useful in subsequent diagnosis. A programmer is free to define a new exception class with properties that can describe all the important related data, but the exception object is a programming interface to the error handler in the catch statement. A lot of the diagnostic information is important to the programmer who will identify and fix the bug, or to the administrator who will correct the configuration of the application.

There is nothing wrong with providing comprehensive diagnostic information through the exception object to the error handler. However, this information may not have anything to do with the basic decision to retry or percolate that is the primary decision a catch clause has to make. There may be occasions where the throw statement doesn't have access to the objects or information needed to log the error and the information has to be passed back to an outer block that has this access. However, if error logging can be made a ubiquitous element of the application environment then it is a better idea for the logging to be done just before the throw instead of passing the buck to the catch.

Since much of the code to report the error is common to many different points in the program, one reasonable strategy is to create the ubiquitous error reporting environment through a set of static methods that are systematically called after an exception object has been allocated and initialized but just before the throw statement. Since the throw statement provides a form of return, it can even be the last statement of the static method. This technique provides three useful features:

  • The class containing the method can contain static pointers to the logging infrastructure used by this application. It can be initialized once and is then available to all points where error information needs to be logged.
  • When a particular problem is hard to diagnose or localize, the logic in these routines can be expanded with additional diagnostic tests to pin down the nature or origin of the problem.
  • They provide natural breakpoints for the debugger when the program is run interactively.

At the catch

Each catch statement is followed by one parameter of a type that extends java.lang.Throwable or System.Exception. A particular exception is caught by the first clause where the type of the exception object allows assignment to the parameter of the clause. This becomes more complicated as library routines provide more precise classification of errors.

When a Java program opens a FileOutputStream, the operation can generate a FileNotFoundException or a SecurityException. When a C# program opens a FileStream for output, it can also generate a SecurityException. However, all the problems that Java bundles under the FileNotFoundException are in the .NET Framework further classified into the ArgumentException, ArgumentNullException, FileNotFoundException, DirectoryNotFoundException, PathTooLongException, and IOException.

A program might reasonably group these detailed exceptions into four response categories:

  1. SecurityException is a problem with the file and directory permissions. The file name and path is probably OK, but the owner of the directory must grant permission for the operation.
  2. ArgumentException, ArgumentNullException, and PathTooLongException  indicate that the file name is absent or has invalid syntax. This cannot be fixed by creating missing file structures. The text of the name must be corrected.
  3. FileNotFoundException and DirectoryNotFoundException indicate that the name could be valid but the necessary file or path need to be created before the operation can proceed.
  4. IOException may mean that the disk is physically failing. However, it is more likely to be any unspecified error not covered by the previous categories, as when you attempt to create a "new" file in a directory where the name already exists (that is, it is the undeclared "FileFoundException").

Unfortunately, the catch statement cannot specify a list of exception classes. If you want to match four specific classes you have to code four specific catch clauses. Each clause is independent. The program cannot "fall through" from one clause to the next, nor can one clause call or transfer control to another clause. This leaves two possible solutions.

catch calls common subroutines

One attractive solution is to develop a package of common static external helper functions that perform common logging and problem determination functions. A program will probably need the same error handling logic for all sorts of files opened at different places for different purposes. Shared logic in a static method can be called from any catch statement in the program. Passed the exception object as an argument, it can perform common logging and additional analysis. Even if a particular analysis is only called from one place, the program may be easier to read if every catch clause is reduced to a method call followed by optional retry logic.

one great big catch

An alternate solution is to only use one catch (everything) clause. Although this produces a generic parameter of indeterminate type, the exception object can be subject to a sequence of class membership (instanceof) expression tests. Since all the exception handling is now done within the scope of a single block, the code handling several different exception classes can share common sequences of statements. This does trap all exceptions, but if it is found that the exception object is of a type not intended to be handled here, it can be cast back to its specific type and rethrown.

impudent suggestion

There is a common thread to both of these suggestions. It holds that the try-catch structure shared by both Java and C# is not quite right. It seems to do the wrong thing and creates problems which any sophisticated program will have to circumvent by creating an effective logical program structure that is entirely different from the separate isolated exception handling clauses that the literal use of multiple catch statements is trying to enforce. One is reluctant to conclude that the generally accepted language syntax is really wrong. To disprove this assertion, someone must provide some counterexamples to show that there are a large class of problems for which this particular language structure is more appropriate.

Summary

No program is perfect. Even if your program were perfect, it is exposed to problems generated by routines that you call. Even if all the library routines were perfect, exceptions can still be generated by hardware problems like a network or power failure. Even if the hardware was perfect, an administrator can always take down a database.

Anything that can go wrong will go wrong anywhere in your program. Java forces a program to deal with all the exceptions that a method is declared to throw. That is a good start, but there are lots of other exceptions that are except from declaration. A program must assume that the standard classes in the runtime library are reliable. Good practice is to assume that everything else will fail in every way that is it possible to fail. Anticipate these exceptions, log them for future analysis, classify them as to source and effect, and eventually recover from them even if the recovery produces no results that are useful to the end user.

There are an impossibly large number of possible sources and types of errors. However, exception handling tends to be driven by the structure of your program. There are points when the program can naturally recover from exceptions, and these points are independent of the type of error.