Exception Handling
The exception handling features in the Java language provide a mechanism for gracefully recovering from a wide variety of unexpected situations in your code.
Even if you've never heard of exceptions in Java, if you do much Java software development, you've likely encountered an exception of some sort. Program "crashes" are often the result of "an exception being thrown." When this happens, something like this is displayed to the console:
Exception in thread "main" java.lang.NullPointerException
at lecture.Driver.main(Driver.java:19)
The exception handling features in the Java language provide a mechanism for gracefully recovering from a wide variety of unexpected situations in your code.
In the first example, I tried to call a method using a reference that was assigned to null
. Since it wasn't referring to an actual object, it was not possible to execute the method. At that point, the normal flow of the program is interrupted by an exception being thrown. In this case, I didn't do anything to handle the exception. As a result, the exception was thrown all the way back the the JVM (Java Virtual Machine) which then displayed the stack trace.
The stack trace is a list of all of the method calls that took place before the exception was thrown. In this case, the exception was thrown from main
(on line 19).
Here is another example of an exception being thrown:
Exception in thread "main" java.lang.NumberFormatException: For input string: "not an integer"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at edu.msoe.cs1021.taylor.Horse.calculatePrice(Horse.java:134)
at edu.msoe.cs1021.taylor.Lab4.displayMarketPrice(Lab4.java:74)
at edu.msoe.cs1021.taylor.Lab4.main(Lab4.java:61)
In this case, I called Integer.parseInt
with "not an integer" as the argument. Of course, this cannot be converted/parsed into an integer. So what should we do? One option would be to just ignore the error (silently fail). If we did this, the parseInt
method would still need to return something. What should we return? Another option would be to just give up and stop the program (that's essentially what happened here).
If you look at the stack trace displayed above, you can see that the exception was thrown in the Integer.parseInt
method which was called by the Horse.calculatePrice
which was called by the Lab4.displayMarketPrice
method which was called by the Lab4.main
method. The stack trace also includes the line numbers on which the each method was called. This helps the developer track down the source of the exception.
Depending on the design of our application, we may want to correct for this incorrect behavior in the Horse.calculatePrice
method or Lab4.displayMarketPrice
method or the Lab4.main
method. The exception handling mechanism in Java provides just the flexibility we need in order to handle error conditions that may be encountered (bad user input, missing file, no network connection, etc...) in a place that makes sense to us.
Exceptions
To summarize the discussion above:
-
An exception represents a condition in which something unexpected/abnormal has occurred.
-
This condition may occur during the normal execution of a program, e.g.,
int x = Integer.parseInt("1.234");
- When an exception occurs, the normal flow of the program is terminated.
- Normal program flow is terminated by
throw
ing and exception. - Once a exception has been thrown, program flow works backwards through the various method calls currently on the stack until the exception is caught.
- An exception is caught by an exception handling routine.
- We can provide exception handling routines or not.
- If no exception handling routines are provided, the Java Virtual Machine will catch and handle the exception.
- In order to catch an exception, we need to be watching for an exception to be thrown.
- We watch for exceptions using a try block.
Consider the following code snippet:
String input = JOptionPane.showInputDialog(null, "How old are you (in years)");
int age;
try {
age = Integer.parseInt(input);
JOptionPane.showMessageDialog(null, "I'm guessing you'll be " + (age+1) + " next year");
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null, "Error! You should enter an integer value");
}
If we enter something other than an integer, the program will display the error message, otherwise it will "guess" how old the user will be next year.
try block
- Code between curly braces following a try statement is part of what we call a try block.
- If, at any time, an exception is thrown while we are in the try block, execution of the code within the try block is immediately halted. Program flow is transferred to the end of the try block.
- Note: an exception can be encountered either by an explicit throw command within the try block or as a result of an un-handled exception thrown by a method which was called within the try block. In the above example, the exception was thrown by the parseInt method and was left unhandled.
catch blocks
- Code between curly braces following a catch statement is part of what we call a catch block.
- A catch block is executed if
- an exception has been encountered in the try block immediately preceding it and
- the type of exception encountered matches the type of exception being caught.
- The type of exception a catch block catches is specified in parentheses after the catch statement. In the above example, the catch block catches NumberFormatException type exceptions.
- Only the first matching catch block after a try block will be executed.
- If no exceptions were encountered during the execution of a try block, the catch block(s) after the try block are ignored. Program flow continues with the statement following the try-catch statement.
- If an exception is encountered, but no matching catch block is present, program control reverts to the calling method.
Types of Exceptions
- In order for an exception to be thrown, it must be an object from the Throwable class, or one of its subclasses.
- The Throwable class has two subclasses:
- Recall that the type of exception being caught must be specified in the catch block's parameter list.
- There are many different types of exceptions.
After studying the Throwable, Error, and Exception classes we can conclude that:
- Error and Exception types are specializations of the Throwable type.
- Exception has all of the characteristics (attributes/behaviors) of a Throwable object, but Exception may specialize (re-implement) some of these characteristics or add other characteristics that are shared by all Exception instances but don't apply to Throwable instances.
- The java.lang package provides a number of subclasses (and subsubclasses, etc) of the Exception class.
- Objects from any of these subclasses can be thrown.
- The diagram below shows some of the types that can be thrown.
Handling Exceptions
- When an object from the Error class is thrown, it represents a serious problem and normally should not be caught.
- When an object from the Exception class, or one of its subclasses, is thrown, it typically should be caught and handled.
- The type of exception a catch block catches is specified in parentheses after the catch statement.
- A thrown exception will be caught if it is the same type or a subtype of the type specified in parentheses.
- For example, if a NumberFormatException, it would be caught by the following catch block:
catch (Exception e) {
// ...
}
since Exception is a superclass of NumberFormatException.
- Once caught, methods from the Throwable class provide information about the exception that was thrown:
- The getMessage() method returns a descriptive string.
- The printStackTrace() method prints all of the methods on the stack, i.e., all of the still active methods called prior to encountering the exception.
- Be sure to catch more specialized exception types (subclasses) first since once a matching catch block is found, all other catch blocks are ignored.
- If a thrown exception is not caught, anywhere in the program, the Java Virtual Machine (JVM) catches it. The JVM handles exceptions by calling printStackTrace() and then terminating the program.
Recapping
Consider the following code:
String input = JOptionPane.showInputDialog(null, "How old are you (in years)");
int age;
try {
age = Integer.parseInt(input);
JOptionPane.showMessageDialog(null, "I'm guessing you'll be " + (age+1) + " next year");
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null, "Error! You should enter an integer value");
} catch (RuntimeException e) {
JOptionPane.showMessageDialog(null, "Error! Some runtime exception other than NumberFormatException was thrown");
JOptionPane.showMessageDialog(null, "Hint: " + e.getMessage());
return;
}
JOptionPane.showMessageDialog(null, "No worries. Either way we'll continue on with our program");
- Recall that, if no exception occurs, all of the code except for the catch blocks will be executed.
- If an exception is thrown while in the try block,
- The first catch block will be executed if the exception thrown is of type NumberFormatException. Once the catch block finishes executing, program execution continues after the last catch block.
- The second catch block will be executed if the exception thrown is of type RuntimeException or any subclass of RuntimeException other than NumberFormatException (because that exception would have already been caught by the first catch block). If the second catch block is executed, program control is returned to the calling method at the end of the catch block, so the "No worries" dialog will not be displayed.
- If no catch block matches the exception thrown, execution of the current method is terminated and the exception is thrown to the calling method (which can either catch it or propagate the exception to the method that called it).
throw statement
- We can explicitly throw exceptions using the following to steps:
- Instantiate the appropriate Throwable object.
- throw a reference to that object.
- An object is said to be a Throwable object if it is an instance of the Throwable class or one of Throwable's subclasses.
- Exception is a subclass of Throwable.
- Here is what it looks like in Java:
throw new Exception("Info about the exception");
Modifying the previous code to throw an exception if the user entered an age greater than 120 would look like this:
String input = JOptionPane.showInputDialog(null, "How old are you (in years)");
int age;
try {
age = Integer.parseInt(input);
if(age>120) {
throw new Exception("Out of bounds");
}
JOptionPane.showMessageDialog(null, "I'm guessing you'll be " + (age+1) + " next year");
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null, "Error! You should enter an integer value");
} catch (RuntimeException e) {
JOptionPane.showMessageDialog(null, "Error! Some runtime exception other than NumberFormatException was thrown");
JOptionPane.showMessageDialog(null, "Hint: " + e.getMessage());
return;
}
JOptionPane.showMessageDialog(null, "No worries. Either way we'll continue on with our program");
Exception throwers
- A method is said to be an exception thrower if it has the potential to throw an exception (directly or indirectly).
- Such a method can be a:
- Catcher -- if it catches a thrown exception,
- Propagator -- if it leaves a exception uncaught (so that it propagates to the calling method, or
- Both if it does some of each.
Unchecked Exception
- Some exceptions are not allowed to propagate back to the JVM, i.e., they must be caught by your code.
- A checked exception is an exception that is checked at compile time.
- Objects from the RuntimeException or one of its subclasses are unchecked exceptions.
- Do not need to be caught by your code.
- Are detected only at runtime.
- Only RuntimeExceptions can be left unchecked.
- In order to propagate a checked exception, the method propagating the exception must explicitly indicate that it may throw a checked exception.
- To indicate that a method may throw a checked exception, the method's header must include a list of checked exceptions that it may throw.
- The syntax makes use of the throws keyword as follows:
void someMethod() throws IOException {
// ...
}
- The exception classes shown in yellow in the figure above are all checked exceptions. That is, the compiler checks to make sure if there is a potential that any of those types of exceptions are thrown, they must be handled.
- Use of the throws keyword is optional for RuntimeExceptions.
The finally Block
- A finally block may be used in conjunction with a try block.
- Code within a finally block is executed regardless of whether an exception was thrown within the try block that it is associated with.
- The finally block is discussed on the File IO page.