The Java Programming Language:
Exceptions and Exception Handling
We have to resign ourselves to the fact that errors are going to occur in our programs. The important thing, of course, is how our programs react to those errors. In particular, how are they to be handled, and by whom (i.e., by which part of the program)? Ultimately, the question is this: Can the program recover from the error, or should it simply be allowed to die?
Object Throwable Error LinkageError ClassCircularityError ClassFormatError ExceptionInInitializerError IncompatibleClassChangeError AbstractMethodError IllegalAccessError InstantiationError NoSuchFieldError NoSuchMethodError NoClassDefFoundError UnsatisfiedLinkError VerifyError ThreadDeath VirtualMachineError InternalError OutOfMemoryError StackOverflowError UnknownError Exception ClassNotFoundException CloneNotSupportedException IllegalAccessException InstantiationException InterruptedException IOException ChangedCharSetException CharacterCodingException CharConversionException ClosedChannelException EOFException FileLockInterruptionException FileNotFoundException IIOException InterruptedIOException MalformedURLException ObjectStreamException ProtocolException RemoteException SocketException SSLException SyncFailedException UnknownHostException UnknownServiceException UnsupportedEncodingException UTFDataFormatException ZipException NoSuchFieldException NoSuchMethodException RunTimeException ArithmeticException ArrayStoreException ClassCastException IllegalArgumentException IllegalThreadStateException NumberFormatException IllegalMonitorStateException IllegalStateException IndexOutOfBoundsException ArrayIndexOutOfBoundsException StringIndexOutOfBoundsException NegativeArraySizeException NullPointerException SecurityExceptionIn this (only partially complete) hierarchy of things that may go wrong, "errors" are generally problems from which programs will not be able to recover (such as a hard disk failure or other hardware problem). Normal programs will neither throw nor catch Throwable objects of type Error. On the other hand, "exceptions" (Throwable objects of type Exception, or any of its subclasses) may be, and often must be, "caught" and "handled", since they represent problems from which a program may be expected to recover, or at least "terminate gracefully".
An exception handler is a piece of code that is automatically invoked when an exception occurs, and may allow the programmer to fix (or at least deal with in some way) an exception when it occurs.
In essence, exception handling provides a structured means for the programmer to "catch" runtime errors in a program, and make them return meaningful information about themselves. The programmer can then choose to ignore the information, or, which is usually the better alternative, to perform certain actions before permitting the program to carry on or terminate.
Here is the syntax for "trying" some code, and "catching" two exceptions that might get "thrown" if either "exceptional circumstance" occurs while you're doing so:
try { // Some code that might cause an exception to be thrown } catch (ExceptionType1 e1) { // Do whatever should be done if e1 occurs } catch (ExceptionType2 e2) { // Do whatever should be done if e2 occurs }Note that in a sequence of "catches" like this, the most "general" one should be last.
Don't use exception handling as a "break statement on steroids". Exceptions should be thrown only in "exceptional" circumstances. For example, if you are reading from a file and "expect" to hit the end-of-file, this is not an exceptional circumstance and should not, therefore, be handled by throwing an EOFException.
class MyException extends Throwable { public MyException() { super(); } public MyException(String message) { super(message); } }
if (somethingWrong) throw new MyException(parameter_list);where exceptionObject is an instance of the class Throwable, or any of the other classes in the exception class hierarchy.
void doSomething() throws ThisException, ThatException { // Body of method goes here ... }
void printStackTrace() void printStackTrace(PrintWriter someStream)can be used to display on the screen (in the first case), or on another stream (in the second), the list of methods visited in the search for a suitable catch block.
Throwable fillInStackTrace()may occasionally be useful to supply information to the trace.
try { // Code to be tried ... } catch (Exception e) { // Code to deal with thrown exception e } finally { // Code that gets executed in any case }Exit from a finally block can be through a return statement, though it may be at the end of its enclosing method in any case, in which case no explicit return is required.
Exception handling is a built-in aspect of the Java programming language. The concept of handling error cases with exceptions is designed to make code more reliable and easier to maintain and read. This tip looks at some best practices for dealing with and handling exceptions.
As a quick refresher, here's the full construct for exception handling:
try { // code which could potentially // throw an exception } catch (TheException1 e) { // code to handle exceptional condition } catch (TheException2 e) { // code to handle next exceptional condition } finally { // code to run whether an exceptional // condition happened or not }
Basically, code that might throw an exception goes within a try block. A specific catch block code runs only if the exception identified for it occurs (or its subclass). However, the code in the finally block runs under all conditions -- this even includes the case when the try-catch code calls return to exit out of the current method.
The try block is required. Additionally, one of the two blocks, catch or finally, must be present, or the compiler will complain.
Given that you can have multiple catch clauses, the system finds the first one that matches the exceptional condition. So, if you have a catch block for an IOException, and a FileNotFoundException happens, this is caught by the general IOException catch clause.
This suggests a best practice for exception handling: Always work with as specific an exception as possible. For instance, if you create a method that could generate a FileNotFoundException, don't declare that method to throw an IOException. If you do, it forces the caller to handle all IOExceptions that are possible, not just FileNotFoundExceptions. You shouldn't get too specific though. In particular, the declared exception thrown by a method should never reveal implementation details.
Another best practice is try to avoid empty catch blocks. In other words, don't ignore exceptions when they happen.
// This code is bad try { ... } catch (AnException e) { }
The designers of system libraries throw exceptions for a reason: to tell you some exceptional condition happened. It's good practice to do something in response, even simply logging the problem. For classes you design, only declare methods to throw exceptions where you want the caller to deal with a problem. If you want to ignore an exception, perhaps because of an automatic retry that happens every minute or two, simply place a comment in the catch block to explicitly say why the exceptional condition is ignored.
// Better try { ... } catch (AnException ignored) { // System automatically retries every minute // Nothing to really do until retry }
One very important practice related to exceptions thrown from methods is documentation. There is a javadoc tag for documenting which exceptions are thrown by a method. It's @throws. Best practices call for all checked exceptions to be documented. Common runtime exceptions should be documented too. For example, in the following method declaration, both the thrown exception and why the throwable exception could be thrown are documented. Don't just have the @throws ExceptionName bit. That doesn't add any more than the method declaration alone.
/** * Loads the class * * @param name * Class name * * @return Resulting Class object * * @throws ClassNotFoundException * If class not found */ public Class loadClass(String name) throws ClassNotFoundException { ... }
For runtime exceptions, an exception here is the parseInt method of Integer. This can throw a NumberFormatException (this is declared as part of the method declaration). However, because this is a RuntimeException, it doesn't have to be declared.
Not only don't runtime exceptions have to be declared, but neither do they have to be checked. For instance, every array access can throw an ArrayIndexOutOfBoundsException. It is not good practice to wrap all array access code within a try-catch block. Doing that makes code difficult to read and maintain.
// This is bad, don't do it: try { for (int i=0, n=args.length; i<n; i++) { System.out.println(args[i]); } } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Ooops"); }
For recoverable conditions, it is good practice to have catch clauses with actions involved. These are typically checked exceptions, although it's also possible with runtime exceptions. Case in point: the previously mentioned parseInt method of Integer. If you are validating user input with the parseInt method, that is certainly a recoverable operation (assuming the user is still available).
While it is possible to create your own custom exceptions, more typically, you can reuse the system exception classes. These are usually sufficient for most needs, though not for every need.
Runtime exceptions that you might use include the following:
The final aspect of exception handling to explore is exception chaining. Introduced with the 1.4 release of the Java 2 Platform, Standard Edition, exception chaining allows you to attach the underlying cause of a problem to the thrown exception. Chaining is not usually meant for an end user to see. Instead, it allows the person debugging a problem to see what caused the underlying problem.
For instance, in the database world, specifically in the JDBC libraries, it is common to have catch clauses for SQLException. Even prior to exception chaining, the SQLException clause had a getNextException method which allowed chaining of exceptions. To find out all the underlying causes of the SQLException, you could have a loop that looks something like the following:
try { // JDBC code } catch (SQLException ex) { while (ex != null) { System.err.println( "SQLState: " + ex.getSQLState()); System.err.println( "Message: " + ex.getMessage()); System.err.println( "Vendor: " + ex.getErrorCode()); System.err.println("-----"); ex = ex.getNextException(); } }
Instead of using the JDBC version of chaining, the standard approach is now the one shown below. Instead of printing the SQL State, message, and error code, just the exception type and message are displayed.
try { // Normal code } catch (AnException ex) { while (ex != null) { System.err.println( "Type: " + ex.getClass().getName()); System.err.println( "Message: " + ex.getMessage()); System.err.println("-----"); ex = ex.getNextException(); } }
There is certainly much more that can be done with exceptions. Hopefully, the practices shown here should help get you started toward better understanding and usage.