Java Exceptions
Contents
- Overview
- InputFile Example
- ListOfNumbers Example
- The throw Statement
- The Throwable Class and Its Subclasses
- Creating Your Own Exception Classes
Overview
When an error occurs within a Java method, the method creates an exception object, which includes information about the type of exception and the state of the program when the error occurred. Exceptions bubble up through the set of methods in the call stack until an appropriate handler is found. Creating an exception object and handing it to the runtime system is called throwing an exception. The exception handler chosen is said to catch the exception. If an appropriate exception handler is not found the program terminates.
readFile { try { open file; determine size of file; allocate memory for file; read file into memory; close the file; } catch (fileOpenFailed) { doSomething; } catch (sizeDeterminationFailed) { doSomething; } catch (memoryAllocationFailed) { doSomething; } catch (readFailed) { doSomething; } catch (fileCloseFailed) { doSomething; } }Exceptions often fall into categories or groups, for example, array exceptions:
Java exception objects must be instances of Throwable or any Throwable descendant. Each leaf class represents a specific type of exception. Each node class represents a group of related exceptions.
- Index is out of range for the size of the array
- Element being inserted into the array is of the wrong type
- Element being searched for is not in the array
Exception | ArrayException | ------------------------------------------------- | | | InvalidIndexException NoSuchElementException ElementTypeExceptionArrayException is a node class and represents any error that can occur when manipulating an array object, including those errors specifically represented by one of its subclasses. A method can catch an exception based on its group or general type by specifying any of the exception's superclasses in the catch statement. For example, to catch all array exceptions regardless of their specific type, an exception handler would specify an ArrayException argument:
catch (ArrayException e) { . . . }This handler would catch all array exceptions including InvalidIndexException, ElementTypeException, and NoSuchElementException. You can find out precisely which type of exception occurred by querying the exception handler parameter e. You could even set up an exception handler that handles any Exception with this handler:
catch (Exception e) { . . . }Exception handlers that are too general can make your code more error prone by catching and handling exceptions that you didn't anticipate and therefore are not correctly handled within the handler.
InputFile Example
Methods must either catch or specify all checked exceptions that can be thrown within the scope of the method. If the compiler detects a method, such as the bolded method below, that doesn't meet this requirement, it issues an error message:
// Note: This class won't compile by design! import java.io.*; public class InputFile { private FileReader in; public InputFile(String filename) { in = new FileReader(filename); } public String getWord() { int c; StringBuffer buf = new StringBuffer(); do { c = in.read(); if (Character.isWhitespace((char)c)) return buf.toString(); else buf.append((char)c); } while (c != -1); return buf.toString(); } }Here is the error message that results:
InputFile.java:8: Warning: Exception java.io.FileNotFoundException must be caught, or it must be declared in throws clause of this method. in = new FileReader(filename); ^The Java language requires that a method either catch or specify all checked exceptions that can be thrown within the scope of that method. Because the InputFile class does neither, the compiler refuses to compile the program and prints an error message.
In addition to the error message shown above, the following error message should appear:
InputFile.java:15: Warning: Exception java.io.IOException must be caught, or it must be declared in throws clause of this method. while ((c = in.read()) != -1) { ^The InputFile class's getWord method reads from the FileReader that was opened in InputFile's constructor. The FileReader read method throws a java.io.IOException if for some reason it can't read from the file. Again, the InputFile class makes no attempt to catch or specify this exception. Thus you see the second error message.
At this point, you have two options. You can either arrange to catch the exceptions within the appropriate methods in the InputFile class, or the InputFile methods can "duck" and allow other methods further up the call stack to catch them. Either way, the InputFile methods must do something, either catch or specify the exceptions, before the InputFile class can be compiled. For the diligent, there's a class, InputFileDeclared, that fixes the bugs in InputFile by specifying the exceptions.
The ListOfNumbers Example
The following example defines and implements a class named ListOfNumbers. The ListOfNumbers class calls two methods from classes in the Java packages that can throw exceptions.
// Note: This class won't compile by design! // See ListOfNumbersDeclared.java or ListOfNumbers.java // for a version of this class that will compile. import java.io.*; import java.util.Vector; public class ListOfNumbers { private Vector victor; public ListOfNumbers () { victor = new Vector(size); for (int i = 0; i < size; i++) victor.addElement(new Integer(i)); } public void writeList() { PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) out.println("Value at: " + i + " = " + victor.elementAt(i)); out.close(); } }Upon construction, ListOfNumbers creates a Vector that contains ten Integer elements with sequential values 0 through 9. The ListOfNumbers class also defines a method named writeList that writes the list of numbers into a text file called OutFile.txt.
The writeList method calls two methods that can throw exceptions. First, the following line invokes the constructor for FileWriter, which throws an IOException if the file cannot be opened for any reason:
out = new PrintWriter(new FileWriter("OutFile.txt"));Second, the Vector class's elementAt method throws an ArrayIndexOutOfBoundsException if you pass in an index whose value is too small (a negative number) or too large (larger than the number of elements currently contained by the Vector). Here's how ListOfNumbers invokes elementAt:
out.println("Value at: " + i + " = " + victor.elementAt(i));If you try to compile the ListOfNumbers class, the compiler prints an error message about the exception thrown by the FileWriter constructor, but does not display an error message about the exception thrown by elementAt. This is because the exception thrown by the FileWriter constructor, IOException, is a checked exception and the exception thrown by the elementAt method, ArrayIndexOutOfBoundsException, is a runtime exception. Java requires that you catch or specify only checked exceptions
The try Block
The first step in constructing an exception handler is to enclose the statements that might throw an exception within a try block.
To construct an exception handler for the writeList method from the ListOfNumbers class, you need to enclose the exception-throwing statements of the writeList method within a try block. You could put each statement that might potentially throw an exception within its own try statement, and provide separate exception handlers for each try. Or you could put all of the writeList statements within a single try statement and associate multiple handlers with it. The following listing uses one try statement for the entire method:
PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) out.println("Value at: " + i + " = " + victor.elementAt(i)); }The try statement governs the statements enclosed within it and defines the scope of any exception handlers associated with it. In other words, if an exception occurs within the try statement, that exception is handled by the appropriate exception handler associated with this try statement. A try statement must be accompanied by at least one catch block or one finally block.
The catch Block(s)
The try statement defines the scope of its associated exception handlers. You associate exception handlers with a try statement by providing one or more catch blocks directly after the try block:try { . . . } catch ( . . . ) { . . . } catch ( . . . ) { . . . } . . .There can be no intervening code between the end of the try statement and the beginning of the first catch statement. The general form of Java's catch statement is:
catch (SomeThrowableObject variableName) { Java statements }As you can see, the catch statement requires a single formal argument. The argument to the catch statement looks like an argument declaration for a method. The argument type, SomeThrowableObject, declares the type of exception that the handler can handle and must be the name of a class that inherits from the Throwable class defined in the java.lang package. When Java programs throw an exception they are really just throwing an object, and only objects that derive from Throwable can be thrown. You'll learn more about throwing exceptions in How to Throw Exceptions.
variableName is the name by which the handler can refer to the exception caught by the handler. For example, the exception handlers for the writeList method (shown later) each call the exception's getMessage method using the exception's declared name e:
You access the instance variables and methods of exceptions in the same manner that you access the instance variables and methods of other objects. getMessage is a method provided by the Throwable class that prints additional information about the error that occurred. The Throwable class also implements two methods for filling in and printing the contents of the execution stack when the exception occurred. Subclasses of Throwable can add other methods or instance variables. To find out what methods an exception implements, check its class definition and definitions for any of its ancestor classes. The catch block contains a series of legal Java statements. These statements are executed if and when the exception handler is invoked. The runtime system invokes the exception handler when the handler is the first one in the call stack whose type matches that of the exception thrown. The writeList method from the ListOfNumbers class uses two exception handlers for its try statement, with one handler for each of the two types of exceptions that can be thrown within the try block -- ArrayIndexOutOfBoundsException and IOException.e.getMessage()try { . . . } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch IOException(e) { System.err.println("Caught IOException: " + e.getMessage()); }
Catching Multiple Exception Types with One Handler
The two exception handlers used by the writeList method are very specialized. Each handles only one type of exception. The Java language allows you to write general exception handlers that handle multiple types of exceptions.
Java exceptions are Throwable objects; they are instances of Throwable or a subclass of Throwable. The Java packages contain numerous classes that derive from Throwable and thus, build a hierarchy of Throwable classes.
Your exception handler can be written to handle any class that inherits from Throwable. If you write a handler for a "leaf" class (a class with no subclasses), you've written a specialized handler: it will only handle exceptions of that specific type. If you write a handler for a "node" class (a class with subclasses), you've written a general handler: it will handle any exception whose type is the node class or any of its subclasses.
Let's modify the writeList method once again. Only this time, let's write it so that it handles both IOExceptions and ArrayIndexOutOfBoundsExceptions. The closest common ancester of IOException and ArrayIndexOutOfBoundsException is the Exception class. An exception handler that handles both types of exceptions looks like this:
try { . . . } catch (Exception e) { System.err.println("Exception caught: " + e.getMessage()); }The Exception class is pretty high in the Throwable class hierarchy. So in addition to the IOException and ArrayIndexOutOfBoundsException types that this exception handler is intended to catch, it will catch numerous other types. Generally speaking, your exception handlers should be more specialized. Handlers that can catch most or all exceptions are typically useless for error recovery because the handler has to determine what type of exception occurred anyway to determine the best recovery strategy. Also, exception handlers that are too general can make code more error prone by catching and handling exceptions that weren't anticipated by the programmer and for which the handler was not intended.
The finally Block
The try block of the writeList method that you've been working with opens a PrintWriter. The program should close that stream before allowing control to pass out of the writeList method. This poses a somewhat complicated problem because writeList's try block has three different exit possibilities:
- The new FileWriter statement failed and threw an IOException.
- The victor.elementAt(i) statement failed and threw an ArrayIndexOutOfBoundsException.
- Everything succeeded and the try block exited normally.
The runtime system always executes the statements within the finally block regardless of what happens within the try block. Regardless of whether control exits the writeList method's try block due to one of the three scenarios listed previously, the code within the finally block will be executed. This is the finally block for the writeList method. It cleans up and closes the PrintWriter.
finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } }
Is the finally Statement Really Necessary?
At first the need for a finally statement may not be immediately apparent. Programmers often ask "Is the finally statement really necessary or is it just sugar for my Java?" In particular, C++ programmers doubt the need for a finally statement because C++ doesn't have one.
The need for a finally statement is not apparent until you consider the following: how does the PrintWriter in the writeList method get closed if you don't provide an exception handler for the ArrayIndexOutOfBoundsException and an ArrayIndexOutOfBoundsException occurs? (It's easy and legal to omit an exception handler for ArrayIndexOutOfBoundsException because it's a runtime exception and the compiler won't alert you that the writeList contains a method call that might throw one.) The answer is that the PrintWriter does not get closed if an ArrayIndexOutOfBoundsException occurs and writeList does not provide a handler for it--unless the writeList provides a finally statement.
There are other benefits to using the finally statement. In the writeList example it is possible to provide for cleanup without the intervention of a finally statement. For example, you could put the code to close the PrintWriter at the end of the try block and again within the exception handler for ArrayIndexOutOfBoundsException, as shown here:
try { . . . out.close(); // don't do this; it duplicates code } catch (ArrayIndexOutOfBoundsException e) { out.close(); // don't do this; it duplicates code System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch IOException(e) { System.err.println("Caught IOException: " + e.getMessage()); }However, this duplicates code, making the code hard to read and prone to errors if you modify the code later. For example, if you add code to the try block that may throw a new type of exception, you will have to remember to close the PrintWriter within the new exception handler (which if you're anything like me, you are bound to forget).
When all of the components are put together, the writeList method looks like this:
public void writeList() { PrintWriter out = null; try { System.out.println("Entering try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < size; i++) out.println("Value at: " + i + " = " + victor.elementAt(i)); } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage()); } catch IOException(e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }This try block in this method has three different exit possibilities:
- The new FileWriter statement fails and throws an IOException.
- The victor.elementAt(i) statement fails and throws an ArrayIndexOutOfBoundsException.
- Everything succeeds and the try statement exits normally.
This section investigates in detail what happens in the writeList method during each of those exit possibilities.
Scenario 1: An IOException Occurs
The new FileWriter("OutFile.txt") statement can fail for any number of reasons: the user doesn't have write permission on the file or directory, the file system is full, or the directory for the file doesn't exist. If any of these situations is true, then the constructor for FileWriter throws an IOException. When the IOException is thrown, the runtime system immediately stops execution of the try block. Then the runtime system attempts to locate an exception handler appropriate for handling an IOException.
The runtime system begins its search at the top of the method call stack. When the exception occurred, the FileWriter constructor was at the top of the call stack. However, the FileWriter constructor doesn't have an appropriate exception handler so the runtime system checks the next method in the method call stack--the writeList method. The writeList method has two exception handlers: one for ArrayIndexOutofBoundsException and one for IOException.
The runtime system checks writeList's handlers in the order that they appear following the try statement. The argument to the first exception handler is ArrayIndexOutofBoundsException, but the exception that was thrown is an IOException. An IOException cannot legally be assigned to an ArrayIndexOutofBoundsException, so the runtime system continues its search for an appropriate exception handler.
The argument to writeList's second exception handler is an IOException. The exception thrown by the FileWriter constructor is also an IOException and can be legally assigned to the handler's IOException argument. Thus, this handler is deemed appropriate and the runtime system executes this handler, which prints this statement:
Caught IOException: OutFile.txtAfter the exception handler has run, the runtime system passes control to the finally block. In this particular scenario, the PrintWriter never was opened, and thus out is null and won't get closed. After the finally block has completed executing, the program continues with the first statement after the finally block.
The complete output that you see from the ListOfNumbers program when an IOException is thrown is this:
Entering try statement Caught IOException: OutFile.txt PrintWriter not open
Scenario 2: An ArrayIndexOutofBoundsException Occurs
This scenario is the same as the first except that a different error occurs during the try block. In this scenario, the argument passed to the Vector's elementAt method is out of bounds. That is, the argument is either less than 0 or is larger than the size of the array. (The way the code is written, this is actually impossible, but let's suppose a bug is introduced into the code when someone modifies it.)
As in to scenario 1, when the exception occurs the runtime system stops execution of the try block and attempts to locate an exception handler suitable for an ArrayIndexOutofBoundsException. The runtime system searches for an appropriate exception handler as it did before. It comes upon the catch statement in the writeList method that handles exceptions of the type ArrayIndexOutofBoundsException. Since the type of the thrown exception matches the type of the exception handler, the runtime system executes this exception handler.
After the exception handler has run, the runtime system passes control to the finally block. In this particular scenario, the PrintWriter did get opened, thus the finally statement closes it. After the finally block has completed executing, the program continues with the first statement after the finally block.
The complete output that you see from the ListOfNumbers program when an ArrayIndexOutofBoundsException is thrown is something like this:
Entering try statement Caught ArrayIndexOutOfBoundsException: 10 >= 10 Closing PrintWriter
Scenario 3: The try block exits normally
In this scenario, all the statements within the scope of the try block execute successfully and throw no exceptions. Execution falls off the end of the try block, and then the runtime system passes control to the finally block. Since everything was successful, the PrintWriter is open when control reaches the finally block, which closes the PrintWriter. Again, after the finally block has completed executing, the program continues with the first statement after the finally block.
Thus, the output that you see from the ListOfNumbers program when no exceptions are thrown is:
Entering try statement Closing PrintWriter
The throw Statement
All Java methods use the throw statement to throw an exception. The throw statement requires a single argument: a throwable object. In the Java system, throwable objects are instances of any subclass of the Throwable class. Here's an example of a throw statement:
throw someThrowableObject;If you attempt to throw an object that is not throwable, the compiler refuses to compile your program and displays an error message similar to the following:
testing.java:10: Cannot throw class java.lang.Integer; it must be a subclass of class java.lang.Throwable. throw new Integer(4); ^Let's look at the throw statement in context. The following method is taken from a class that implements a common stack object. The pop method removes the top element from the stack and returns it:
public Object pop() throws EmptyStackException { Object obj; if (size == 0) throw new EmptyStackException(); obj = objectAt(size - 1); setObjectAt(size - 1, null); size--; return obj; }The pop method checks to see if there are any elements on the stack. If the stack is empty (its size is equal to 0), then pop instantiates a new EmptyStackException object and throws it. The EmptyStackException class is defined in the java.util package. Later pages in this lesson describe how you can create your own exception classes. For now, all you really need to remember is that you can throw only objects that inherit from the java.lang.Throwable class.
The throws Clause
You'll notice that the declaration of the pop method contains this clause:
throws EmptyStackExceptionThe throws clause specifies that the method can throw an EmptyStackException. The Java language requires that methods either catch or specify all checked exceptions that can be thrown within the scope of that method. You do this with the throws clause of the method declaration.
Throwable Class and Its Subclasses
Throwable has two direct descendants:
- Errors
Systemic errors thrown by the virtual machine, such as a dynamic linking errors. Typical rare, most Java programs should not bother to catch Errors.
- Exceptions
Programatic errors. These are the ones to throw and catch. The Exception class has many descendants.
Runtime Exceptions
Exceptions that occur within the Java virtual machine, for example, a NullPointerException.
Because runtime exceptions are so ubiquitous, the costs of attempting to catch or specify all of them all the time outweighs any benefits derived, so the compiler allows runtime exceptions to go uncaught and unspecified.
The Java packages define several RuntimeException classes. You can catch these exceptions just like other exceptions. However, a method is not required to specify that it throws RuntimeExceptions. In addition, you can create your own RuntimeException subclasses.
Creating Your Own Exception Classes
When you design a package of Java classes that collaborate to provide some useful function to your users, you work hard to ensure that your classes interact well together and that their interfaces are easy to understand and use. You should spend just as much time thinking about and designing the exceptions that your classes throw.
Suppose you are writing a linked list class that you're planning to distribute as freeware. Among other methods, your linked list class supports these methods:
objectAt(int n)
Returns the object in the nth position in the list.firstObject
Returns the first object in the list.indexOf(Object n)
Searches the list for the specified Object and returns its position in the list.
What Can Go Wrong?
Because many programmers will be using your linked list class, you can be assured that many will misuse or abuse your class and its methods. Also, some legitimate calls to your linked list's methods may result in an undefined result. Regardless, in the face of errors, you want your linked list class to be as robust as possible, to do something reasonable about errors, and to communicate errors back to the calling program. However, you can't anticipate how each user of your linked list class will want the object to behave under adversity. So, often the best thing to do when an error occurs is to throw an exception.
Each of the methods supported by your linked list might throw an exception under certain conditions, and each method might throw a different type of exception than the others. For example,
objectAt
Throws an exception if the integer passed into the method is less than 0 or larger than the number of objects currently in the list.firstObject
Throws an exception if the list contains no objects.indexOf
Throws an exception if the object passed into the method is not in the list.But what type of exception should each method throw? Should it be an exception provided with the Java development environment? Or should you create your own?
Choosing the Exception Type to Throw
When faced with choosing the type of exception to throw, you have two choices:
- Use one written by someone else. The Java development environment provides a lot of exception classes that you could use.
- Write one of your own.
You should go to the trouble of writing your own exception classes if you answer "yes" to any of the following questions. Otherwise, you can probably get away with using someone else's:
- Do you need an exception type that isn't represented by those in the Java development environment?
- Would it help your users if they could differentiate your exceptions from those thrown by classes written by other vendors?
- Does your code throw more than one related exception?
- If you use someone else's exceptions, will your users have access to those exceptions? A similar question is: Should your package be independent and self-contained?
Your linked list class can throw multiple exceptions, and it would be convenient to be able to catch all exceptions thrown by the linked list with one exception handler. Also, if you plan to distribute your linked list in a package, all related code should be packaged together. Thus for the linked list, you should create your own exception class hierarchy.
![]()
LinkedListException is the parent class of all the possible exceptions that can be thrown by the linked list class. Users of your linked list class can write a single exception handler to handle all linked list exceptions with a catch statement like this:
catch (LinkedListException) { . . . }Or, users could write more specialized handlers for each subclass of LinkedListException.
Choosing a Superclass
The diagram above does not indicate the superclass of the LinkedListException class. Java exceptions must be Throwable objects (they must be instances of Throwable or a subclass of Throwable). So, your temptation might be to make LinkedListExceptiona subclass of Throwable. However, the java.lang package provides two Throwable subclasses that further divide the type of problems that can occur within a Java program: Errors and Exceptions. Most of the applets and applications that you write will throw objects that are Exceptions. (Errors are reserved for serious hard errors that occur deep in the system.)
Theoretically, any Exception subclass could be used as the parent class of LinkedListException. However, a quick perusal of those classes show that they are either too specialized or completely unrelated to LinkedListException to be appropriate. Thus, the parent class of LinkedListException should be Exception.
Because runtime exceptions don't have to be specified in the throws clause of a method, many packages developers ask: "Isn't it just easier if I make all of my exception inherit from RuntimeException?". The bottom line is that you shouldn't subclass RuntimeException unless your class really is a runtime exception! For most of you, this means "No, your exceptions shouldn't inherit from RuntimeException."
Naming Conventions
It's good practice to append the word "Exception" to the end of all classes that inherit (directly or indirectly) from the Exception class. Similarly, classes that inherit from the Error class should end with the string "Error". The following error message is one of two similar error messages you will see if you try to compile the class InputFile, because the InputFile class contains calls to methods that throw exceptions when an error occurs:
The Java language requires that methods either catch or specify all checked exceptions that can be thrown within the scope of that method. .) If the compiler detects a method, such as those in InputFile, that doesn't meet this requirement, it issues an error message like the one shown above and refuses to compile the program.InputFile.java:8: Warning: Exception java.io.FileNotFoundException must be caught, or it must be declared in throws clause of this method. in = new FileReader(filename); ^Let's look at InputFile in more detail and see what's going on.
The InputFile class wraps a FileReader and provides a method, getWord, for reading a word from the current position in the reader.
The compiler prints the first error message because of the bold line in the above code listing. The bold line creates a new FileReader object and uses it to open a file whose name is passed into the FileReader constructor.// Note: This class won't compile by design! import java.io.*; public class InputFile { private FileReader in; public InputFile(String filename) { in = new FileReader(filename); } public String getWord() { int c; StringBuffer buf = new StringBuffer(); do { c = in.read(); if (Character.isWhitespace((char)c)) return buf.toString(); else buf.append((char)c); } while (c != -1); return buf.toString(); } }So what should the FileReader do if the named file does not exist on the file system? Well, that depends on what the program using the FileReader wants to do. The implementers of FileReader have no idea what the InputFile class wants to do if the file does not exist. Should the FileReader kill the program? Should it try an alternate filename? Should it just create a file of the indicated name? There's no possible way the FileReader implementers could choose a solution that would suit every user of FileReader. So, they punted, or rather, threw, an exception. If the file named in the argument to the FileReader constructor does not exist on the file system, the constructor throws a java.io.FileNotFoundException. By throwing an exception, FileReader allows the calling method to handle the error in whatever way is most appropriate for it.
As you can see from the code, the InputFile class completely ignores the fact that the FileReader constructor can throw an exception. However, as stated previously, the Java language requires that a method either catch or specify all checked exceptions that can be thrown within the scope of that method. Because the InputFile class does neither, the compiler refuses to compile the program and prints an error message.
In addition to the first error message shown above, you also see the following similar error message when you compile the InputFile class:
The InputFile class's getWord method reads from the FileReader that was opened in InputFile's constructor. The FileReader read method throws a java.io.IOException if for some reason it can't read from the file. Again, the InputFile class makes no attempt to catch or specify this exception. Thus you see the second error message.InputFile.java:15: Warning: Exception java.io.IOException must be caught, or it must be declared in throws clause of this method. while ((c = in.read()) != -1) { ^At this point, you have two options. You can either arrange to catch the exceptions within the appropriate methods in the InputFile class, or the InputFile methods can "duck" and allow other methods further up the call stack to catch them. Either way, the InputFile methods must do something, either catch or specify the exceptions, before the InputFile class can be compiled. For the diligent, there's a class, InputFileDeclared, that fixes the bugs in InputFile by specifying the exceptions.
The next section describes in further detail Java's Catch or Specify Requirement. The subsequent sections show you how to comply to the requirement.
Java's Catch or Specify Requirement
As stated previously, Java requires that a method either catch or specify all checked exceptions that can be thrown within the scope of the method. This requirement has several components that need further description: "catch", "specify," "checked exceptions," and "exceptions that can be thrown within the scope of the method."
Catch
A method can catch an exception by providing an exception handler for that type of exception. The next page, Dealing with Exceptions, introduces an example program, talks about catching exceptions, and shows you how to write an exception handler for the example program.
The next page, Dealing with Exceptions, talks about specifying exceptions that a method throws and shows you how to do it.
Runtime exceptions can occur anywhere in a program and in a typical program can be very numerous. The cost of checking for runtime exceptions often exceeds the benefit of catching or specifying them. Thus the compiler does not require that you catch or specify runtime exceptions, although you can. Checked exceptions are exceptions that are not runtime exceptions and are checked by the compiler; the compiler checks that these exceptions are caught or specified.
Some consider this a loophole in Java's exception handling mechanism, and programmers are tempted to make all exceptions runtime exceptions. In general, this is not recommended. Runtime Exceptions--The Controversy contains a thorough discussion about when and how to use runtime exceptions.