Return to lecture notes index

15-100 Lecture 15 (Friday, February 20, 2004)

Catching Exceptions

Today we're going to be learning about Java's exception-handling mechanisms. Remember back to the lectures where we talked about number conversion, parsing Strings to ints or doubles or floats, using methods like Integer.parseInt(). What happened if we tried to parse "asdf" to an int? The program crashed, without really telling the user what had gone wrong. How do we handle this more elegantly?

The answer is to use Java exceptions for situations such as this. Java allows the programmer to specify what the program should do if it encounters an error like this, by "catching" the exception that was "thrown" by the Integer.parseInt() method.

catch and throw are actual Java keywords for handling exceptions. Remember when we learned about Java I/O, and learned that if we were using BufferedReader and InputStreamReader, we had to include "throws IOException" after our method declaration for the main method. We did this because Java requires that if a method might "throw" an Exception that must be reported (most Java exceptions fall into this category), we must declare in the method declaration the possibility that this exception could be thrown. Any time we use I/O, there is a possibility that the input/output could fail, and this could throw an IOException.

The code below looks much like code we've seen before to read in input from the keyboard and parse it to an int.


import java.io.*;
class ExceptionExample {
  public static void main(String[] args) throws IOException, NumberFormatException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
    System.out.print("Enter a price in dollars with cents as a decimal: ");
    //this line can cause an IOException (checked)
    String moneyString = br.readLine(); //now we are reading a double as a String...so we need to parse it
		
    //this can also throw a NumberFormatException (unchecked)
    double moneyDouble = Double.parseDouble(moneyString);
		
    moneyDouble *= 100;
    System.out.println("You entered " + (int)moneyDouble + " cents");
  }
}

The only difference between the above and what you have normally seen is that we've added NumberFormatException to the list of exceptions that might be thrown by the main method. NumberFormatException is one of the relatively few exceptions in Java that do not have to be explicitly reported in the method declaration, but you have the option of doing so. In this case, we are reporting it because if the user enters "asdf" at the prompt, Java will try to parse it to an int, and the Integer.parseInt() method will throw a NumberFormatException, crashing our program and causing it to print out the message below:


Exception in thread "main" java.lang.NumberFormatException: For input string: "47d"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)...	

So what happens before Java prints out that message? When ever an exception is thrown during a program java stops following its sequential order and goes into an exception handling mode. (Unless it is caught when it is first created) Java will throw the error out of the method that caused it to the method that called the exception causing method. Java will will keep on throwing the error out of the methods in the reverse order that the methods were called until it reaches a point in the program where the programmer catches that exception. If the exception is never caught the program dies and Java prints a similar version of the example message above. If the exception is caught Java will execute the catch block that caught it and then execute all code that follows the catch block (this means that a good deal of your code can be skipped over when Java goes into it's exception handling mode).

Side note: there are two different forms of exceptions: checked and unchecked. A checked Exception is usually thrown due to an unavoidable error or the programmer slightly messing up the coding. ( An IOException will be thrown if the programmer .close() a BufferedReader and then tries to use the BufferedReader later in the program. However, the keyboard messing up would also cause an IOException and neither the programmer nor the user can avoid this error.) These checked errors must be either caught or thrown in the methods where they are thrown. An unchecked exception does not need to be declared. Unchecked exceptions are due to runtime errors (such as the user entering a String instead of a double) However, do not worry about this minor point. I would suggest that you throw or catch all exceptions that you expect could occur. If you miss declaring a checked exception the Java compiler will point this out to you.

So how do we go about reporting the error to the user in a more descriptive way, without actually crashing our program? We use the Java keyword try, which tells Java that the code inside the try block (inside the scope of the squiggly braces opened after the keyword 'try') might throw an exception. (ie in this block look for an exception to be thrown...let's be prepared to deal with it). This is demonstrated in the code below:


import java.io.*;
class ExceptionExample {
  //in the past we have dealt with thrown exceptions by declaring them in the method's signature
  public static void main(String[] args) throws IOException, NumberFormatException {
    try {
      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
      System.out.print("Enter a price in dollars with cents as a decimal: ");
      String moneyString = br.readLine();
    }
		
    // Note - this will not compile, because int number is only valuable
    // inside the scope of this try block.  Variables only exist
    // inside the set of squiggly braces in which they are declared!
    // See the fix for this in the next block of code, below.
    //It also won't compile because we have a try block without a catch
    //block
    moneyDouble *= 100;
    System.out.println("You entered " + (int)moneyDouble + " cents");
  }
}

However, the try block in itself is not enough to handle the two exceptions that might be thrown by the main method (NumberFormatException and IOException). If exceptions are being thrown, we must "catch" them. The "try" block alerts Java that it must be ready to catch an exception; we now need a "catch" block to tell it what to do when/if it does catch an exception.

When we close the try block by adding the close-squiggly brace, we then open a "catch" block as shown below. Inside the parentheses, we specify what type of exception this catch block is designed to catch, and of course, just like any other argument, we need to give this Exception variable a name (e.g., nfe, ioe). Inside the squiggly braces for the catch block, we tell Java what to do if it catches this type of exception in the try block above. It's a good idea to use these catch blocks to tell the user what they did wrong (why the exception was thrown). Also, note that we can have a separate catch block for each type of exception that might be thrown inside the try block.


import java.io.*;
class ExceptionExample {
  public static void main(String[] args) {
    //note: you have to initialize number outside of the try block otherwise 
    //it might not get initialized (if an exception is thrown) and thus the 
    //code won't compile.
    double moneyDouble = 0.0;
    String moneyString = "";
		
    System.out.print("Enter a price in dollars with cents as a decimal: ");
		
    try{
      //cause an IOException by putting "br.close();" here
      String moneyString = br.readLine(); //now we are reading a double as a String!
		
      //this can also throw an error (unreported)
      double moneyDouble = Double.parseDouble(moneyString);
		
      //put these two lines in here to make program run logically...even though 
      //they don't throw an exception
      moneyDouble *= 100;
      System.out.println("You entered " + (int)moneyDouble + " cents");
		
    }
    //this is thrown in the parseDouble method
    catch(NumberFormatException nfe){
      System.out.print("Woops you didn't enter a number");
      return;
    }
    //note this exception will be thrown if you put br.close(); before the line
    //br.readLine() 
    catch(IOException ioe) {
      System.out.println("Error reading from input.  Bad keyboard!");
      return;
    }
    System.out.println("Success!");
  }//end main
}//end class

In the code above, we use the return keyword by itself, without specifying anything to return. This tells Java to leave the method at that point, and since we're inside the main method, it means to simply end the program. We need to have this keyword inside the catch blocks because without it, after the catch block executed, execution would drop to the System.out.println("Success") line, and if we didn't successfully completely finish the program we don't want to print that we did! So inside the catch blocks, the last thing we do is tell the program to exit once it's printed to the user what went wrong to cause the exception to be thrown.

What if we want to print out what type of exception was thrown? You can simply call the System.out.println() method on the Exception (in this case, we've called it 'nfe'), and it will print out "java.lang.NumberFormatException".


catch(NumberFormatException nfe) {
  System.out.println("Woops you didn't enter a number");
  System.out.println(nfe);
  return;
}

Catch blocks have another interesting property. A thrown exception will enter the first catch block it encounters that the type of catch declaration fits the exception. Since all Exceptions belong to the Exception class, you can write a catch (Exception e) block and it will catch all possible exceptions that the partnered try block can throw. Thus the following code will catch both the IOException and NumberFormatException:


import java.io.*;
class ExceptionExample {
  public static void main(String[] args) {
    double moneyDouble = 0.0;
    String moneyString = "";
		
    System.out.print("Enter a price in dollars with cents as a decimal: ");
		
    try{
      String moneyString = br.readLine();
      double moneyDouble = Double.parseDouble(number string);
		
      moneyDouble *= 100;
      System.out.println("You entered " + (int)moneyDouble + " cents");
    }
    catch(Exception e){
      System.out.print("Woops an exception occurred.");
      return;
    }
    System.out.println("Success!");
  }//end main
}//end class

Notice how the above code can only give a vague description of what error was throw because it could catch any type of exception. Here is one way to use Exception, but still keep your exception messages specific:


    //this is thrown in the parseDouble method
    catch(NumberFormatException nfe){
      System.out.print("Woops you didn't enter a number");
      return;
    }
    //note this exception will be thrown if you put br.close(); before br.readLine();
    catch(IOException ioe) {
      System.out.println("Error reading from input.  Bad keyboard!");
      return;
    }
    //catches all of the other exceptions
    catch(Exception e){
      System.out.print("Woops an exception occurred.");
      return;
    }

The first block will catch all NumberFormatException, the second block will catch all IOException while the third catch block will catch all exceptions that are not NumberFormatException or IOException (since the previous blocks have already caught them). Note that the following code will NOT compile:


    //catches all exceptions
    catch(Exception e){
      System.out.print("Woops an exception occurred.");
      return;
    }
    //these two catches will never catch any exceptions...can you see why?
    catch(NumberFormatException nfe){
      System.out.print("Woops you didn't enter a number");
      return;
    }
    catch(IOException ioe) {
      System.out.println("Error reading from input.  Bad keyboard!");
      return;
    }

Remember the previous statement..."A thrown exception will enter the first catch block it encounters that the type of catch declaration fits the exception." Since the type Exception matches all exceptions the thrown exception will always enter the first catch block. So the other two catch blocks are excessive and are never used when placed in this order. Java won't let you write this redundant code and the compiler will give you an error message when you try to compile.