Exceptions

Java and C++ exceptions are remarkably similar. When an error occurs in a program, the code that experiences the error can "throw" an exception. This causes flow of control to be "unrolled" from each "called" method to each "calling" method. At any point in this process, the exception can be: ignored, caught and handled, or caught and rethrown. Java's exception handling capability - The example below totally ignores the possibility of exceptions. Yet unlike C++, the divide-by-zero situation does not "dribble off into the bit bucket" and produce a core dump; it is implicitly "caught" and "handled" by the Java Virtual Machine.
   class ExceptionDemos {      // error
      public static void main( String[] args ) {
         int x = 5;
         x = x / (x - x);
         System.out.println( "Looks good to me" );
   }  }
   
   // Exception in thread "main" java.lang.ArithmeticException: / by zero
   //         at ExceptionDemos.main(ExceptionDemos.java:4)
This is how the defensive programmer can test for and handle exceptions. The normal application code is placed in a try block, and the exception handler code is placed in one of potentially several catch blocks.

Java exceptions are objects - they have private implementation and public interface. They can exercise intelligence, or perform a service, on behalf of their "client". Here, the exception object "e" is interacting intelligently with println().

   class ExceptionDemos {      // catch everything - println(e)
      public static void main( String[] args ) {
         try {
            int x = 5;
            x = x / (x - x);
         } catch (Exception e) {
            System.out.println( "---" + e + "---" );
         }
         System.out.println( "Looks good to me" );
   }  }
   
   // ---java.lang.ArithmeticException: / by zero---
   // Looks good to me
The previous example just passed the exception object to println(). Here are two methods that Exception objects know how to respond to.
   class ExceptionDemos {      // catch everything - e.method()
      public static void main( String[] args ) {
         try {
            int x = 5;
            x = x / (x - x);
         } catch (Exception e) {
            e.printStackTrace();
            System.out.println( "---" + e.getMessage() + "---" );
         }
         System.out.println( "Looks good to me" );
   }  }
   
   // java.lang.ArithmeticException: / by zero
   //         at ExceptionDemos.main(ExceptionDemos.java:34)
   // ---/ by zero---
   // Looks good to me
All Exception objects are defined somewhere in the Java standard library. Here is part of the class hierarchy.
   Throwable
      |
      +--- Error
      |       |
      |       +--- VirtualMachineError
      |               |
      |               +--- OutOfMemoryError
      |
      +--- Exception
              |
              +--- IOException
              |       |
              |       +--- EOFException
              |
              +--- NoSuchMethodException
              |
              +--- RuntimeException
                      |
                      +--- ArithmeticException
                      |
                      +--- ArrayIndexOutOfBoundsException
You can put as many catch blocks after the try as you desire. The order in which they appear should be "most specific" (derived class) to "most general" (base class). This will guarantee that a more specific exception is not "short circuited" by a more general exception that usurps the handling responsibility.
   class ExceptionDemos {      // be more discriminating
      public static void main( String[] args ) {
         try {
            int x = 5;
            x = x / (x - x);
         } catch (ArithmeticException e) {
            System.out.println( "+++" + e + "+++" );
         } catch (Exception e) {
            System.out.println( "---" + e + "---" );
         } catch (Throwable e) {
            System.out.println( "%%%" + e + "%%%" );
         }
         System.out.println( "Looks good to me" );
   }  }
   
   // +++java.lang.ArithmeticException: / by zero+++
   // Looks good to me
If you forget, and order the catch blocks from "more general" to "more specific", the code will not compile.
   class ExceptionDemos {      // out of order - won't compile
      public static void main( String[] args ) {
         try {
            int x = 5;
            x = x / (x - x);
         } catch (Exception e) {
            System.out.println( "---" + e + "---" );
         } catch (ArithmeticException e) {
            System.out.println( "+++" + e + "+++" );
         }
   }  }
   
   // ExceptionDemos.java:15: catch not reached.
   //    } catch (ArithmeticException e) {
   
   ////////////////////////////// lab - readLine() \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - divideByZero \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - parseInt() \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
[Source: Peter Haggar, "Effective Exception Handling in Java", Java Report, April 1999, p57]

The finally keyword is probably the best addition to the Java exception handling model over the C++ model. finally enables the execution of code to occur whether an exception occurred or not. It is good for: maintaining the internal state of an object when an exception occurs, and cleaning up non-object resources.

   class ExceptionDemos {      // "finally" is guaranteed to execute
      public static void main( String[] args ) {
         openConnection();
         try {
            performTransaction();
         } catch (DomainException e )
            ...
         } finally {
            closeConnection();
   }  }  }
The magic of finally blocks are that they are always executed. However, a few circumstances exist that can prevent execution of a finally block - If an exception occurs that does not match any specified catch blocks, then the finally is executed before the exception is allowed to propagate.
   class ExceptionDemos {      // error, no catch, finally, propogate exception
      public static void main( String[] args ) {
         try {
            int[] array = new int[2];
            array[2] = 42;
         } catch (ArithmeticException e) {
            System.out.println( "+++" + e + "+++" );
         } finally {
            System.out.println( "finally happened" );
         }
         System.out.println( "after try/catch" );
   }  }
   
   // finally happened
   // java.lang.ArrayIndexOutOfBoundsException: 2
   //         at ExceptionDemos.main(ExceptionDemos.java:12)
If an exception occurs that matches a catch block, then the finally is executed after the catch, and the exception does not continue to propagate.
   class ExceptionDemos {      // error, catch, finally, normal flow
      public static void main( String[] args ) {
         try {
            int[] array = new int[2];
            array[2] = 42;
         } catch (ArithmeticException e) {
            System.out.println( "+++" + e + "+++" );
         } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println( "---" + e + "---" );
         } finally {
            System.out.println( "finally happened" );
         }
         System.out.println( "after try/catch" );
   }  }
   
   // ---java.lang.ArrayIndexOutOfBoundsException: 2---
   // finally happened
   // after try/catch
Next, an exception occurs that matches a catch block. That catch block proceeds to throw a brand new exception. But before the new exception is allowed to propagate, the original try block's finally is executed. Notice that the new ArithmeticException is not caught by a "sibling" catch block.
   class ExceptionDemos {      // error, catch, throw new, finally, propogate
      public static void main( String[] args ) {
         try {
            int[] array = new int[2];
            array[2] = 42;
         } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println( "+++" + e + "+++" );
            throw new ArithmeticException();
         } catch (ArithmeticException e) {
            System.out.println( "+++" + e + "+++" );
         } finally {
            System.out.println( "finally happened" );
         }
         System.out.println( "after try/catch" );
   }  }
   
   // +++java.lang.ArrayIndexOutOfBoundsException: 2+++
   // finally happened
   // Exception in thread "main" java.lang.ArithmeticException
   //         at ExceptionDemos.main(ExceptionDemos.java:136)
If no exception occurs, the finally executes after the try.
   class ExceptionDemos {      // no error, finally, normal flow
      public static void main( String[] args ) {
         try {
            int[] array = new int[2];
            array[1] = 42;
         } catch (ArithmeticException e) {
            System.out.println( "+++" + e + "+++" );
         } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println( "---" + e + "---" );
         } finally {
            System.out.println( "finally happened" );
         }
         System.out.println( "after try/catch" );
   }  }
   
   // finally happened
   // after try/catch
finally blocks are always executed. This includes flow of control associated with all the below: The first two interations of the for loop show the finally block happening for normal execution. The last two interations show it happening for break and continue. method2() shows the finally block happening for return and throwing an exception.
   class ExceptionDemos {      // finally always executes
      public static void main( String[] args ) {
         for (int i=0;     ; i++) 
            try {
               System.out.print( "i is " + i );
               if (i == 3) break;
               if (i == 2) continue;
            } finally {
               System.out.println( "   loop's finally" );
            }
         System.out.println( "method1 returned " + method1( false ) );
         System.out.println( "method1 returned " + method1( true  ) );
         System.out.println( "method2 returned " + method2( false ) );
         System.out.println( "method2 returned " + method2( true  ) );
      }
      public static int method1( boolean doThrow ) {
         try {
            if (doThrow) throw new Exception();
            return 1;
         } catch (Exception e) {
            return 11;
      }  }
      public static int method2( boolean doThrow ) {
         try {
            if (doThrow) throw new Exception();
            return 2;
         } finally {
            return 22;
   }  }  }
   
   // i is 0   loop's finally
   // i is 1   loop's finally
   // i is 2   loop's finally
   // i is 3   loop's finally
   // method1 returned 1
   // method1 returned 11
   // method2 returned 22
   // method2 returned 22

Throwing exceptions

To generate an exception, you create an exception object and use the throw keyword to launch it. There are 3 different "categories" of exception objects. These are represented by the Java standard library classes of: Error, RuntimeException, and Exception. Exceptions that belong to the last category are referred to as "checked exceptions". To encourage the writing of robust code, Java requires that if a method throws an exception that is derived from the Exception class, then the user of that method must make clear what action is to be taken when that problem arises. The "handle or declare" rule spells out your options. You can either - In the following example, inner() generates the exception, outer() "declares", and main() "handles".
   class ExceptionDemos {      // 
      public static void main( String[] args ) {              // HANDLE or declare
         try {
            outer();
         } catch (java.io.IOException e) {
            System.out.println( "---" + e + "---" );
      }  }
      public static void outer() throws java.io.IOException { // handle or DECLARE
         inner();
         throw new java.io.IOException( "from outer" );
      }
      public static void inner() throws java.io.IOException { // handle or DECLARE
         throw new java.io.IOException( "from inner" );
   }  }
   
   // ---java.lang.IOException: from inner---
"Many view the throws clause in Java as a savior." [Haggar]   It is where you can document all the checked exceptions that can propagate from a method. It alerts all callers of your method of the checked exceptions that it can generate. The compiler makes sure you either "handle" them with a catch block, or "declare" them in the throws clause on the enclosing method. This feature is a two-edged sword: it makes the code self-documenting, but it also makes it harder to modify later.

If we wanted to change the previous inner() method to throw a new checked exception (see below), then we not only have to update the signature of inner(), but we also have to go to every method that calls inner() (and potentially every method that calls a method that calls inner()) and change them accordingly. One way around this is to be very "general" (or non-committal) to start with. The following code has done just that. All main() and outer() are coupled to (AND all they are capable of dealing with) are very amorphous objects of type Exception.

   class ExceptionDemos {      // 
      public static void main( String[] args ) {
         try {
            outer();
         } catch (Exception e) {
            System.out.println( "---" + e + "---" );
      }  }
      public static void outer() throws Exception {
         inner();
         throw new java.io.IOException( "from outer" );
      }
      public static void inner() throws java.io.IOException,NoSuchMethodException {
         int i = 2;
         if (i == 1) throw new java.io.IOException( "from inner" );
         else        throw new NoSuchMethodException( "from inner" );
   }  }
   
   // ---java.lang.NoSuchMethodException: from inner---

Nested exceptions

[Source: Peter Haggar, "Effective Exception Handling in Java", Java Report, April 1999, pp58-60]

You can place try/catch/finally, try/catch, or try/finally blocks anywhere in your Java code. These blocks can be nested within each other. In doing so, the code be become confusing. But, a more serious side effect is that exceptions can get lost or hidden from the caller.

The example below throws four distinct exceptions (at //4, //6, //9, and //11). As fas as the original caller can tell, only one exception was really thrown (the exception from //11 shows up at //1). The exception thrown at //4 was caught at //5 and not rethrown. The exceptions thrown at //6 and //9 were not caught by any code. What happened to them? In short, they are lost. They are also referred to as hidden. The exception thrown at //9 hid the one thrown at //6. The exception thrown at //11 hid both of them. This could be very bad, because the original problems were not addressed where the problem was known, and, the problems are not knowable in any outer scope.

The best way to deal with this situation is to "wrap" any existing exceptions inside of the new exception you are throwing. This way, the receiver of the new exception will have information about all errors, and critical error information will not be lost.

To review, only one exception can propagate from a try/catch/finally block even though many can be thrown from within it. Only the last exception thrown will be seen by the caller, while others will be hidden or lost.

   class ExceptionDemos {      // nested exceptions
      public static void main( String[] args ) {
         try {
            System.out.println( "main: calling nestingTest" );
            nestingTest();
         } catch (Exception e) {     //1
            System.out.println( "main catch: caught - " + e.getMessage() );
      }  }
      public static void nestingTest() throws Exception {
         try {                       //2
            System.out.println( "outer try" );
            try {                    //3
               System.out.println( "inner try" );
               throw new Exception( "thrown from inner try" );                 //4
            } catch (Exception e) {  //5
               System.out.println( "inner catch: caught - " + e.getMessage() );
               throw new Exception( "thrown from inner catch" );               //6
            } finally {              //7
               try {                 //8
                  System.out.println( "inner finally's try" );
                  throw new Exception( "thrown from inner finally's try" );    //9
               } finally {           //10
                  System.out.println( "inner finally's finally" );
                  throw new Exception( "thrown from inner finally's finally"); //11
            }  }
         } catch (Exception e) {     //12
            System.out.println( "outer catch: caught - " + e.getMessage() );
            throw e;
         } finally {                 //13
            System.out.println( "outer finally" );
   }  }  }
   
   // main: calling nestingTest
   // outer try                  //2
   // inner try                  //3
   // inner catch: caught - thrown from inner try                //5
   // inner finally's try        //8
   // inner finally's finally    //10
   // outer catch: caught - thrown from inner finally's finally  //12
   // outer finally                                              //13
   // main catch: caught - thrown from inner finally's finally   //1

Defining exceptions

"User-defined" exceptions should not inherit directly from the Java standard library's master exception base class java.lang.Throwable. Instead, one of the three derived classes described previously should be used - Error, RuntimeException, or Exception.

The Exception class has a constructor that accepts a String argument. The ApplException class has been designed to accept a String object during initialization and pass that object to its base class. Notice how that String object finds its way to standard-out when the exception is subsequently caught and output.

   class ApplException extends Exception {   // application-defined exception
      public ApplException( String in ) {
         super( in );
      }
      public String tootYourHorn() {
         return "I am a legend in my own mind";
   }  }
   class ExceptionDemos {
      public static void foo() throws ApplException {     // handle or DECLARE
         throw new ApplException( "a string argument" );
      }
      public static void main( String[] args ) {          // HANDLE or declare
         try {
            foo();
         } catch (ApplException e) {
            System.out.println( "+++" + e + "+++" );
            System.out.println( "+++" + e.tootYourHorn() + "+++" );
         } catch (Exception e) {
            System.out.println( "---" + e + "---" );
   }  }  }
   
   // +++ApplException: a string argument+++
   // +++I am a legend in my own mind+++
Because ApplException derives from Exception, the caller of method foo() must follow the "handle or declare" rule. In the example above, the former option is chosen. In the example below, the latter option is chosen. Note that the exception goes unhandled, and a run-time error results.
   class ApplException extends Exception {
      public ApplException( String in ) {
         super( in );
   }  }
   class ExceptionDemos {
      public static void foo() throws ApplException {        // handle or DECLARE
         throw new ApplException( "a string argument" );
      }
      public static void main( String[] args ) throws ApplException {  // DECLARE
         foo();
   }  }
   
   // ApplException: a string argument
   //         at ExceptionDemos.foo(ExceptionDemos.java:9)
   //         at ExceptionDemos.main(ExceptionDemos.java:12)
   
   ////////////////////////////// lab - StackExcept \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
   ////////////////////////////// lab - nestedExceptions \\\\\\\\\\\\\\\\\\\\\\\\\\
To "catch and rethrow" an exception, the syntax is slightly different than C++. Below, foo() throws an exception, main() handles the exception, and then it rethrows the exception to the next enclosing calling context. Since it is main() that is passing on the exception, the JVM is having to ultimately intercede.
   class ExceptionDemos {
      public static void foo() throws java.io.IOException {
         throw new java.io.IOException( "a string argument" );
      }
      public static void main( String[] args ) throws java.io.IOException {
         try {
            foo();
         } catch (java.io.IOException e) {
            System.out.println( "---" + e + "---" );
            throw e;
   }  }  }
   
   // ---java.io.IOException: a string argument---
   // java.io.IOException: a string argument
   //         at ExceptionDemos.main(ExceptionDemos.java:12)

Exceptions and performance

The Haggar article discussed three performance issues related to exceptions.
  1. The cost of throwing an exception
  2. The cost of try/catch blocks
  3. The cost of "traditional" programming
1. Java exceptions are objects, and object creation/administration is relatively expensive. Throwing an exception involves object creation and administration. "I recommend exceptions be used only for error conditions and not for control flow [see the next section] ... When exceptions are used for error/failure conditions, I am not too concerned with speed. I want my code to run fast when it's working, and I normally don't care how long it takes to fail."

2. "Placing try/catch blocks in your Java code can actually slow it down, even when exceptions are not thrown." Consider the following code:

   for (int i=0; i < array.length; i++)
      try {
         array[i] = i;
      } catch (Exception e) {
         // handle exception
      }
Peter Haggar described that for large arrays and a 1.1.4 JVM, this code ran 16% faster when the try/catch block was removed. With JDK 1.2, there was no difference in speed. Placing the for loop inside the try/catch block also fixed the performance penalty. Apparently, even when exceptions are not thrown, there is a measurable amount of overhead associated with the presence of try/catch blocks. "In general, it is better programming practice to place try/catch blocks outside of loops."

3. In a "traditional" Java for loop, two checks are performed each iteration: the i < array.length check, and the ArrayIndexOutOfBoundsException check. Peter Haggar set-up a demo similar to the code below. The first loop is "traditional", the second loop omits the first check. He was able to demonstrate that the second loop is 17% faster with a 1.1.4 JVM, and no difference at all when using JDK 1.2. The numbers documented below also show no significant difference. "I do not recommend using this technique casually. It can be confusing and is not a standard way to program ... Your mileage is entirely a function of the different optimizations used in vendor JVMs."

   import java.util.Date;    // use exception to terminate a loop
   
   class ExceptionDemos {
      public static void main( String[] args ) {
         int[] array = new int[ Integer.parseInt(args[0]) ];
         Date start = new Date();
         for (int i=1; i < array.length; i++)   // "traditional" loop
            array[i] = (int) Math.sqrt( i );
            // array[i] = (int) Math.sqrt( array[i-1] );
         Date end = new Date();
         System.out.println( "normal loop ---- " + (end.getTime() - start.getTime()));
         start = new Date();
         try {
            for (int i=1;         ; i++)        // no when-to-exit-loop check
               array[i] = (int) Math.sqrt( i );
               // array[i] = (int) Math.sqrt( array[i-1] );
         } catch (Exception e) { }
         end = new Date();
         System.out.println( "exception loop - " + (end.getTime() - start.getTime()));
   }  }
   
   // D:> java ExceptionDemos 100000
   // normal loop ---- 60   50   110    50
   // exception loop - 50   60    50   110
   
   // D:> java ExceptionDemos 1000000
   // normal loop ---- 550   540
   // exception loop - 550   550
   
   // D:> java ExceptionDemos 10000000
   // normal loop ---- 5770   5770
   // exception loop - 5660   5600
   
   // array[i] = (int) Math.sqrt( array[i-1] );
   
   // D:> java ExceptionDemos 100000
   // normal loop ---- 0   0   50
   // exception loop - 0  60   50
   
   // D:> java ExceptionDemos 1000000
   // normal loop ---- 330   270   330
   // exception loop - 330   280   280
   
   // D:> java ExceptionDemos 10000000
   // normal loop ---- 3130   3290
   // exception loop - 3070   3130

How to use exceptions

When and when not to use exceptions

[Source: Peter Haggar, "Effective Exception Handling in Java", Java Report, April 1999, pp60-61]

Exception handling was devised as a robust replacement for traditional error handling techniques. Some believe exception handling should be used for any and all error conditions and thus completely avoiding traditional error return checking. I believe that exception handling can be overused. It should not be used exclusively; or for control flow as a replacement for if/else logic. I believe a more moderate approach is necessary whereby exception handling is used in conjunction with traditional error handling techniques. Here is an example of traditional error handling.

   MyInputStream in = new MyInputStream( "filename" );
   int data = in.getData();
   while (data != 0) {             //1
      // do something with data
      data = in.getData();
   }
At //1 we are checking to see if the data value returned from the getData() call is non-zero. If it is zero, we assume we are at the end of the stream. This makes sense and is intuitive. Now let's change this code by taking a hard line on exception handling. We will not rely on return code values, but use exceptions.
   MyInputStream in = new MyInputStream( "filename" );
   int data;
   while (true) {
      try {           //1
         // getData() throws NoMoreDataException when data is done
         data = in.getData();
      } catch (NoMoreDataException e) {
         break;
      }
      // do something with data
   }
Notice at //1 we are placing a try/catch block around the call to getData(). That method no longer returns zero when the stream is empty, it throws NoMoreDataException. Even though the new code is a bit uglier than the original, some people believe it represents the way code should be written with exceptions and that returning error codes should never be done. I disagree. I think the original is much more intuitive even though it relies on the older methods of dealing with errors or unexpected results. I think exceptions can be overused, and the revised version is a good example of it.

In some situations, no reasonable end-of-stream marker can be identified or defined. But instead of adding exception handling, "the most reasonable design is to add an explicit eof() method that should be called before any read from the stream." [Gosling, pp159-160]

   MyInputStream in = new MyInputStream( "filename" );
   int data;
   while ( ! in.eof()) {             //1
      data = in.getData();
      // do something with data
   }
Exceptions should be used for conditions outside the expected behavior of code. In the example above, we expect to get to the end of the stream, therefore a simple zero return from getData() is appropriate and intuitive. I don't think throwing an exception in this case is wise, as this is not an exceptional condition, but an expected one. We would not, however, expect that the stream is corrupted. That type of condition would warrant an exception being generated. The point is not to use exceptions for all conditions, but to use them where it makes sense - where exceptional condition exist.

Object state management

[Source: Peter Haggar, "Effective Exception Handling in Java", Java Report, April 1999, p65]

"What good is it to throw an exception if you leave an object in an invalid undefined state? After all, the code catching the exception you throw may handle it and recover and call your code again. If your object was left in a bad state, there is a chance that the code will fail. This situation can be more difficult to debug that if you just terminated the program on the first exception instead of trying to recover from it. When throwing exceptions, it is important to consider what state your object is in." What is wrong with the code below?

   private int     numElements;
   private TheList myList;
   public void add( Object o ) throws SomeException {
      numElements++;                                     //1
      if (myList.maxElements() < numElements) {
         // reallocate myList
         // copy from old to new
         // possibly throws SomeException
      }
      myList.addToList( o );   // possibly throws SomeException
   }
If an exception is thrown after //1, the object is now in an invalid state. The fix is simple in this case - move the increment of numElements to the end of the method. "This ensures the counter is accurate because we increment it only after we have successfully added the object to the list. This is a simple example, but it exposes a potentially serious class of problems" - object state management. When and where an exception is thrown, you need to be aware of: open files, open sockets, remote object state, open transactions, non-atomic application logic.

"You should review all the places where you generate exceptions to see if you are leaving your objects in a valid state ... Unfortunately, this is extremely difficult to do properly ... The finally clause can and should be used to help preserve object state."

The throws clause

In the "Throwing exceptions" section above, it was noted that checked exceptions must always be "handled or declared". This means that if you want to add a checked exception to a low-level worker method, you have two choices:
  1. Handle the exception in the low-level method
  2. Modify higher-level method(s) to handle or declare the exception
The first option will not work if the low-level method does not have the means to resolve the exception. The second option requires adding the new exception to the throws clause of the low-level method. This necessitates changing all the code that calls the low-level method, for that code must now deal with "handling or declaring".

The throws clause is a useful feature, but it can also be painful if you're not careful. "The moral of this story is not to add exception handling at the end of the development cycle. Your error handling strategy should be designed in from the beginning." [Haggar, p58]

A different author suggests the strategy of using an "unchecked exception" instead of a checked exception. The "handle or declare" rule does not apply for exceptions that inherit from either Error or RuntimeException.

Exceptions and inheritance

As a matter of practice, an overriding method should declare exactly the same signature as the overridden method. As a matter of syntax, the derived class method may declare a subset of checked exceptions in its throws clause, but, it may not declare a superset.
   class ExceptionDemos {
      static class Base {
         public void foo() throws NoSuchMethodException, java.io.IOException {
         //// public void foo() throws NoSuchMethodException {
            throw new NoSuchMethodException( "from Base.foo" );
      }  }
      static class Derived extends Base {
         public void foo() throws java.io.IOException {
         //// public void foo() throws java.io.IOException, NoSuchMethodException {
         //// ERROR: The method void foo() declared in class Derived cannot
         //// override the method of the same signature declared in class Base.
         //// Their throws clauses are incompatible.
            throw new java.io.IOException( "from Derived.foo" );
      }  }
      public static void main( String[] args ) {
         try {
            new Derived().foo();
         } catch (Exception e) {
            System.out.println( e );
   }  }  }
   
   // java.io.IOException: from Derived.foo
As a matter of design, derived classes should not violate the "100% rule". They should not "cancel out" functionality inherited from the base class. If client code is expecting an instance of the base class, it should not be surprised if it receives an instance of the derived class instead (Liskov Substitution Principle).

There are two Date classes in the Java standard library: java.util.Date, and java.sql.Date. The latter inherits from the former. The former has a method called getHours(). The latter chooses not to support that method by throwing the unchecked exception IllegalArgumentException as its implementation.

"The danger with classes like this is that methods that take arguments of the base type will typically not be written so that they can safely deal with objects of the derived type." If these methods receive an instance of the latter class, the application aborts. The problem in this case is that java.sql.Date "is a" java.util.Date is wrong. "This appears to be a case where the designer used inheritance because it saved having to write half a dozen functions. That convenience comes at a high price, though; it prevents the compiler from detecting that an object of type java.sql.Date is being passed to a function that will call invalid methods on it."

The design should have used aggregation/delegation instead of inheritance. The class java.sql.Date "has a" instance of class java.util.Date. When the client code calls getYear(), the former simply delegates to the latter. When the client code calls getHours() the compiler quits, because getHours() is not defined in class java.sql.Date.

[Source: Pete Becker, "Common Design Mistakes", C/C++ Users Journal, Jan 2000, p73]

Using checked versus unchecked exceptions

Maxim 29: Consider the unchecked model of exception handling for large-scale systems design.

When systems are comparatively small (less than 50k LOC), explicitly documenting and mandating the throwing and handling of exceptions at small and many levels of granularily may be quite manageable.

As systems grow in size, the need for centralization increases. Checked exceptions do not support the centralization of error handling at a single point unless every method is complicit in the knowledge of which exceptions may be thrown at the lowest levels. The management of throws clauses can become a maintenance nightmare. And, the design of some participating classes may prohibit them from passing the exceptions along.

"Unchecked exceptions provide a means to throw an exception at any arbitrary point - to be caught higher up - without any intervening method needing to know that it passed it on .... A large C++ system typically centralizes the handling of exceptions at several points, with the most serious conditions being handled at higher points and less severe conditions being handled closed to the source."

[Source: Steve Ball, "Exception Handling in Large Systems", Java Report, July 2000, pp93-94]

Using checked exceptions, I had redundant error handling logic throughout the application - I really did not have much choice. After I switched to unchecked exceptions, I found I could remove the redundancies in the code, fix any inconsistencies, and handle errors much more reliably. If a new exception is added, I could add a single catch block in the one well-defined handler module - without the nasty side effect of declaring thrown exceptions on tons of methods in my system.

After arguing over this "politically incorrect" architecture style with one project lead, he went off and decided to give the coding standard a test on a nontrivial persistence framework. He converted all exceptions from checked to unchecked and did a refactoring pass. As a result, he reduced his source code base by a whooping 30%! He reported the code looked cleaner, read better, was simpler, and finally, he said, he improved the error handling capabilities of the code.

In the best case, checked exceptions introduce redundant error handling in your code. In the worst case, people ignore significant error handling scenarios in their code with a catch-all-and-do-nothing block.

[Source: Todd Lauinger, "To Check or Not to Check", Java Report, January 2001, pp24-26]

Praxis 19: Consider the drawback to the throws clause.
Praxis 20: Be specific and comprehensive with the throws clause.

[Source: Peter Haggar, Practical Java, Addison-Wesley, 2000]