Return to the Java Tips
Handling Exceptions
From: Core Java Technologies Tech Tips for November 4, 2003. Subscribe at http://developer.java.sun.com/servlet/SessionServlet HANDLING EXCEPTIONS Often programmers who are new to the Java programming language see exceptions as little more than annoyances to be ignored and dismissed. The advantage of handling exceptions is that you are forced to think about what could go wrong with your code at runtime. This Tech Tip will present techniques for handling exceptions. Here you'll learn why it's best to deal with exceptions close to their source and as specifically as possible. The goal of exception handling is to help users better run your software. Consider what happens when a user tries to read from a file. The user specifies the name of the file as a command line parameter. In response, a File object is created from the name of the file (a String). A FileReader object is created to read the contents of the File. A BufferedReader is then chained to facilitate the reading of the text contents of the File. What is not being taken into account are all of the things that could go wrong. For example, the user might not specify a file name when the program is run. Or the user might specify a file name, but no file exists that corresponds to that name. Taking account of things that could go wrong is what exception handling is about. A good way to see the value of handling exceptions is to see what happens when you do not take any particular exception handling actions. Let's start with the following program, BadFileAccess: import java.io.File; import java.io.FileReader; import java.io.BufferedReader; public class BadFileAccess { // do NOT code like this public static void main(String[] args) { String fileName = args[0]; File aFile = new File(fileName); FileReader fileReader = new FileReader(aFile); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } } } BadFileAccess does not compile. If you try to compile the program, the compiler complains that there are two unreported exceptions: a java.io.FileNotFoundException and a java.io.IOException. FileNotFoundException extends IOException, which in turn, extends java.io.Exception. Instead of taking some specific actions to handle the exceptions, you might simply treat the exceptions generically, that is, by taking advantage of the exceptions hierarchy. In other words, you might consider both exceptions as instances of the Exception class like this: import java.io.File; import java.io.FileReader; import java.io.BufferedReader; public class BadFileAccess { // do NOT code like this public static void main(String[] args) { String fileName = args[0]; File aFile = new File(fileName); try { FileReader fileReader = new FileReader(aFile); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } } catch (Exception e) { // especially avoid coding like this } } } Compile the code. You should see that it now compiles. What you don't see yet is that it contains many runtime errors. To see one of these errors, run the code without specifying a parameter, like this: java BadFileAccess When you try this, you should get an ArrayIndexOutOfBoundsException. The exception is thrown by the reference to args[0] in the program. The exception forces the program to quit. This might be what you want to happen if a user tries to run the program without specifying a file name. But seeing ArrayIndexOutOfBoundsException might not help an end user realize that they have to to enter the name of a legitimate file. Now let's look at a what you could do to handle the exception. One tempting approach is to use the technique described in the January 09, 2001 Tech Tip titled Handling Uncaught Exceptions. Using that technique, you include the line String fileName = args[0]; in the try block. Here's what the BadFileAccess program looks like with the new line: import java.io.File; import java.io.FileReader; import java.io.BufferedReader; public class BadFileAccess { // do NOT code like this // this is the WORST version public static void main(String[] args) { try{ String fileName = args[0]; File aFile = new File(fileName); FileReader fileReader = new FileReader(aFile); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } } catch (Exception e) { // especially avoid coding like this } } } Compile and run the code as before. The program compiles, but now there's no indication of the ArrayIndexOutOfBoundsException when you run the program. So this technique is not a good one for alerting the user to the problem. What you need to do here is trap the ArrayIndexOutOfBoundsException before the catch block for the generic Exception, and provide a helpful message to the user. Here's what the BadFileAccess program looks like with the addition of the trapping code and the helpful message: import java.io.File; import java.io.FileReader; import java.io.BufferedReader; public class BadFileAccess { // do NOT code like this public static void main(String[] args) { try { String fileName = args[0]; File aFile = new File(fileName); FileReader fileReader = new FileReader(aFile); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } } catch (ArrayIndexOutOfBoundsException e) { System.out.println("correct usage: " + "java BadFileAccess"); } catch (Exception e) { // especially avoid coding like this } } } Compile and run the updated program as before. When you run it, you should see the message: correct usage: java BadFileAccess It's easy to imagine a main() method in the program where different Arrays are accessed. In that case, you might not want the same message issued for all ArrayIndexOutOfBoundsExceptions. In general, it's best to handle each exception as close to its cause as possible. To do this, you need to move the line String fileName = args[0]; and declare and initialize the variable outside of the try block like this: import java.io.File; import java.io.FileReader; import java.io.BufferedReader; public class BadFileAccess { // do NOT code like this public static void main(String[] args) { String fileName = null; try { fileName = args[0]; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("correct usage: " + "" + "java BadFileAccess "); } try { File aFile = new File(fileName); FileReader fileReader = new FileReader(aFile); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } } catch (Exception e) { // especially avoid coding like this } } } Now let's handle the case of a user specifying an invalid file name. Assume in this example that the file named noFile does not exist. Execute the following command: java BadFileAccess noFile.txt The good news is that nothing seems to be amiss. When you execute the command, you should not see an error message. Of course, that is exactly the problem. You should get some indication that no file exists with the name noFile.txt. Recall that the FileReader constructor can throw a FileNotFoundException. To handle the invalid file type of exception, add another catch block: import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.io.FileNotFoundException; public class BadFileAccess { // do NOT code like this public static void main(String[] args) { String fileName = null; try { fileName = args[0]; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("correct usage: " + "java BadFileAccess "); } try { File aFile = new File(fileName); FileReader fileReader = new FileReader(aFile); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } }catch (FileNotFoundException e){ System.out.println(" There is no file at " + fileName); } catch (Exception e) { // especially avoid coding like this } } } Compile the program and run it, specifying noFile.txt as the file name. You should now see the message: There is no file at noFile.txt At this point, you have added code that covers two exceptional cases: running the program without specifying a file name, and running the program specifying an invalid file name. But there's more that you can do to help the user. For example, you can indicate that not specifying a file name or specifying an invalid file name leads to a problem in locating the file. In addition, you can create your own exception depending on the situation. For example, if no file name is specified, you create a FileNotFoundException with an appropriate error message. You are no longer catching an ArrayIndexOutOfBoundsException. You test to see if there is one command line parameter. If there is, you try to open a file at that location. If there is not, you throw a new FileNotFoundException. If no file exists at the path the user specifies, you create a FileNotFoundException with an error message appropriate for that situation. import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.io.FileNotFoundException; public class BadFileAccess { // do NOT code like this private static String fileName; public static void main(String[] args) { try { FileReader fileReader = new FileReader( getFile(args)); BufferedReader buffReader = new BufferedReader(fileReader); while (true) { System.out.println(buffReader.readLine()); } }catch (FileNotFoundException e){ System.out.println( "Could not locate a file: " + e.getMessage()); } catch (Exception e) { // especially avoid coding like this } } private static File getFile(String[] args) throws FileNotFoundException { if(args.length == 1){ fileName = args[0]; } else { throw new FileNotFoundException( "correct usage: " + "java BadFileAccess "); } File aFile = new File(fileName); if (aFile.exists()) { return aFile; } else { throw new FileNotFoundException( "There is no file at " + fileName); } } } Compile the program and run it without specifying a file name. The program throws a FileNotFoundException, and displays the message: Could not locate a file: correct usage: java BadFileAccess Run the program with an invalid file name, for example: java BadFileAccess noFile.txt The program throws a FileNotFoundException, and displays the message: Could not locate a file: There is no file at noFile.txt You can avoid creating multiple instances of a FileNotFoundException by following the advice of the April 22, 2003 Tech Tip on reusing exceptions. That tip shows how to use a Singleton, setting its properties for each instance of the same exception. Alternatively, you can handle the exception in the getFile() method without rethrowing. In any case, if you read from an actual text file, the following block causes a problem that you have yet to address. while (true) { System.out.println(buffReader.readLine()); } Here's an example of the problem. Create a plain text file named realFile.txt. Fill it with sample text such as "This is a sample document." Now run your application like this. java BadFileAccess realFile.txt You should see output that contains the line "This is a sample document." That line is followed by the word "null" on succeeding lines. The "null" lines continue to fill the output until you halt execution. This continuous result happens because there is nothing in your program to stop reading from the file when it reaches the end. What can you do to help the user here? The readLine() method from the BufferedReader class can throw an IOException. There is not much users can do if they encounter an IOException at that point, but you can include a message that makes it clear to them what happened. For example, the following program, FileAccess, displays an explanatory message if there is an IOException: import java.io.FileReader; import java.io.FileNotFoundException; import java.io.File; import java.io.BufferedReader; import java.io.IOException; public class FileAccess { private static String fileName; public static void main(String[] args) { FileReader fileReader = null; try { fileReader = new FileReader(getFile(args)); } catch (FileNotFoundException e) { System.out.println( "Could not locate a file: " + e.getMessage()); } readFile(fileReader); } private static File getFile(String[] args) throws FileNotFoundException { try { fileName = args[0]; } catch (ArrayIndexOutOfBoundsException e) { throw new FileNotFoundException( "correct usage: " + "java FileAccess "); } File aFile = new File(fileName); if (aFile.exists()) { return aFile; } else { throw new FileNotFoundException( "There is no file at " + fileName); } } private static void readFile( FileReader fileReader) { BufferedReader buffReader = new BufferedReader(fileReader); String temp = ""; try { System.out.println( "== Beginning of the file: " + fileName + " =="); while ((temp = buffReader.readLine()) != null) { System.out.println(temp); } } catch (IOException e) { System.out.println("There was a problem reading:" + fileName); } System.out.println("== End of the file: " + fileName + " =="); } } If you run the application like this: java BadFileAccess realFile.txt You should see this: == Beginning of the file: realFile.txt == This a sample document. == End of the file: realFile.txt == For more information about handling exceptions, see Catching and Handling Exceptions in the Java Tutorial.
[report a broken link by clicking here]