Welcome to the Java Mistakes Page!
- As a Java programming instructor, you see a lot of strange errors. Eventually, you start to recognize certain patterns or favorite ways that novice programmers get mixed up. The mistakes documented on this page are all ones that I've seen a dozen times or more. They're all easy to fix, if you can recognize them!
This page is designed to be a resource both for new Java programmers and for new Java instructors. Click on the 'Java Page' banner for my main Java page, where you can find a few applets and links to lots of Java resources.
JavaSoft has a nice 'New to Java' section in their training web site, but I think you have to join JDC to use it (free). Check it out: New2Java at java.sun.com .
If you have a common kind of mistake that you would like me to analyze and add to this page, please send mail to ziring@home.com .
Jump right to a particular mistake:
- Naming the Class Differently from its File Name
- Comparing Strings with ==
- Forgetting to Initialize Object Arrays
- Putting Several Public Classes in One File
- Shadowing an Attribute with a Local Variable
- Forgetting to call a Superclass Constructors
- Catching Exceptions Incorrectly
- Returning void from an Accessor method
- Calling Instance Methods from main()
- Treating Strings as In/Out Parameters
- Declaring a Constructor as a Method
- Forgetting to Cast Object Data Types
- Extending Interfaces
- Calling Superclass Methods and Forgetting to Use the Return Value
- Forgetting to Add AWT Components
- Neglecting Import Statements
- Forgetting to Start Threads
- Using Deprecated java.io.DataInputStream readLine()
- Assigning and passing double literals as floats.
- Naming the Class Differently from its File Name
- All Java systems I've used, including all the Javasoft JDKs, expect the source code of a public class to be stored in a file with the same name as the class, plus the extension .java. Neglecting to follow this convention can lead to a variety of problems, all of which crop of during compiliation.Beginning students often forget this guideline, and name their file something about their assignment, like Lab6.java.
Mistake Example File Lab6.java - public class Airplane extends Vehicle Seat pilot; public Airplane() { pilot = new Seat(); } }
Corrected Example File Airplane.java - public class Airplane extends Vehicle Seat pilot; public Airplane() { pilot = new Seat(); } }
- Comparing Strings with ==
- Java strings are objects of the class java.lang.String. The Java == operator, when applied to objects, merely tests the references for equality! Sometimes, students misunderstand the semantics of the == and try to use it for comparing the contents of strings.
Mistake Example // find out if the first argument is "-a" if (args[0] == "-a") { optionsAll = true; }
Corrected Example // find out if the first argument is "-a" if ("-a".equals(args[0])) { optionsAll = true; }
- Forgetting to Initialize Object Arrays
- A Java array declared for an object type is really an array of object references. Creating the array, with new, simply creates empty references. The elements of the array are all set to null. To actually fill the array, you have to set each element to a real object reference. Many students misunderstand this; they think that creating an object array creates all the actual objects. (In many cases, this is a concept that the student carries over from C++, where creating an array of objects does create all the objects by calling their default constructor.)In the example below, the student wants to create three StringBuffer objects to hold some data. This code will compile, but will throw a NullPointerException on the last line, where one of the (non-existent) objects is used.
Mistake Example // Create an array of data buffers StringBuffer [] myTempBuffers; myTempBuffers = new StringBuffer[3]; myTempBuffers[0].add(data);
Mistake Example // Create an array of data buffers and initialize it. StringBuffer [] myTempBuffers; myTempBuffers = new StringBuffer[3]; for (int ix = 0; ix < myTempBuffers.length; ix++) myTempBuffers[ix] = new StringBuffer(); myTempBuffers[0].add(data);
- Putting Several Public Classes in One File
- Java source code files have a special relationship to the classes in those files. The rules can be summarized as:
- Any Java class is defined in exactly one source file
- At most one public class may be defined by any one source file
- If a public class appears in a source file, the name of the file and the name of the class must be the same.
- Shadowing an Attribute with a Local Variable
- Java permits the programmer to declare local variables inside methods that have the same name as class attributes. The local variable takes precedence, and is used instead of the attribute everywhere it appears; the local variable is said to 'shadow' the attribute.Occasionally, this mistake will cause a compiler error, usually when the shadowed attribute and the local variable are different types. More commonly, this mistake will cause no errors or exceptions, and the student will be left mystified about their unexpected results.
Mistake Example public class Point3 { int i = 0; int j = 0; int k = 0; public boolean hits(Point [] p2list) { for(int i = 0; i < p2list.length; i++) { Point p2 = p2list[i]; if (p2.x == i && p2.y == j) return true; } return false; } }
Corrected Examples // One way to fix the problem int i = 0; int j = 0; int k = 0; public boolean hits(Point [] p2list) { for(int i = 0; i < p2list.length; i++) { Point p2 = p2list[i]; if (p2.x == this.i && p2.y == this.j) return true; } return false; }
// A better way to fix the problem int x = 0; int y = 0; int z = 0; public boolean hits(Point [] p2list) { for(int i = 0; i < p2list.length; i++) { Point p2 = p2list[i]; if (p2.x == x && p2.y == y) return true; } return false; }
- Forgetting to call a Superclass Constructors
- When a Java class extends another class, every constructor of the subclass must call some constructor of the superclass (somehow). Usually, this is accomplished by putting a call to the superclass constructor into the subclass constructor as the first line, something like super(x). If the first line of the subclass constructor is not a call to a superclass constructor, the compiler inserts such a call, but with no parameters: super().Sometimes, a student will forget this requirement. Usually, this is not a problem: the superclass constructor call inserted by the compiler works just fine. However, if the superclass does not have an accessible default no-parameter constructor, then the compiler will complain. In the example below, all constructors of the superclass java.io.File take 1 or 2 parameters.
Mistake Example public class JavaClassFile extends File { String classname; public JavaClassFile(String cl) { classname = cl; } }
Corrected Example public class JavaClassFile extends File { String classname; public JavaClassFile(String cl) { super(cl + ".class"); classname = cl; } }
- Catching Exceptions Incorrectly
- Java's exception handling system is sophisticated and rich, but difficult for novice programmers to conceptualize right away. Students with a strong C++ or Ada background will usually have no trouble, but C and Fortran programmers often will. The examples below show several different exception coding mistakes.Here, the exception is missing a name. This mistake is caught by the compiler, and the student will usually fix the problem by themselves.
Mistake Example try { stream1 = new FileInputStream("data.txt"); } catch (IOException) { message("Could not open data.txt"); }
Corrected Example try { stream1 = new FileInputStream("data.txt"); } catch (IOException ie) { message("Could not open data.txt: " + ie); }
Mistake Example try { serviceSocket.setSoTimeout(1000); newsock = serviceSocket.accept(); } catch (IOException ie) { message("Error accepting connection."); } catch (SocketException se) { message("Error setting time-out."); }
Corrected Example try { serviceSocket.setSoTimeout(1000); newsock = serviceSocket.accept(); } catch (SocketException se) { message("Error setting time-out."); } catch (IOException ie) { message("Error accepting connection."); }
Mistake Example public void waitFor(int sec) { Thread.sleep(sec * 1000); }
Corrected Example public void waitFor(int sec) throws InterruptedException { Thread.sleep(sec * 1000); }
- Returning void from an Accessor method
- This is a very simple mistake, the student creates a method to get a value (an accessor) but gives it the return type void. The fix is to give the accessor method the type of the data it is expected to return.
Mistake Example public class Line { private Point start, end; public void getStart() { return start; } }
Corrected Example public class Line { private Point start, end; public Point getStart() { return start; } }
- Calling Instance Methods from main()
- The initial entry point for a Java program must be a public static method named main. Because main is a static method, it cannot call instance methods directly. Students sometimes get confused about main's status, and try to call regular methods without creating objects. This kind of mistake is especially prevalent early in a Java programming class, when the students are writing very small programs.
Mistake Example public class DivTest { boolean divisible(int x, int y) { return (x % y == 0); } public static void main(String [] args) { int v1 = 14; int v2 = 9; // next line causes a compiler error message if (divisible(v1,v2)) { System.out.println(v1 + " is a multiple of " + v2); } else { System.out.println(v2 + " does not divide " + v1); } } }
Corrected Example 1 public class DivTest { int modulus; public DivTest(int m) { modulus = m; } boolean divisible(int x) { return (x % modulus == 0); } public static void main(String [] args) { int v1 = 14; int v2 = 9; DivTest tester = new DivTest(v2); // next line causes a compiler error message if (tester.divisible(v1) { System.out.println(v1 + " is a multiple of " + v2); } else { System.out.println(v2 + " does not divide " + v1); } } }
Corrected Example 2 public class DivTest { static boolean divisible(int x, int y) { return (x % y == 0); } public static void main(String [] args) { int v1 = 14; int v2 = 9; // next line causes a compiler error message if (divisible(v1,v2)) { System.out.println(v1 + " is a multiple of " + v2); } else { System.out.println(v2 + " does not divide " + v1); } } }
- Treating Strings as In/Out Parameters
- Java's string class, java.lang.String, provides a good encapsulation of string data. However, Java strings are (1) immutable, and (2) objects. Therefore, they cannot be treated as simple character buffers; they must be treated as immutable opaque objects. Sometimes, a student will attempt to treat a String parameter to a method as if it were a character array passed by reference (as in a C char array, or a C++ STL string object). This is a fairly subtle mistake, but one which usually passes the compiler.
Mistake Example public static void main(String args[]) { String test1 = "Today is "; appendTodaysDate(test1); System.out.println(test1); } public void appendTodaysDate(String line) { line = line + (new Date()).toString(); }
Corrected Example 1 public static void main(String args[]) { String test1 = "Today is "; test1 = appendTodaysDate(test1); System.out.println(test1); } public String appendTodaysDate(String line) { return (line + (new Date()).toString()); }
Corrected Example 2 public static void main(String args[]) { StringBuffer test1 = new StringBuffer("Today is "); appendTodaysDate(test1); System.out.println(test1.toString()); } public void appendTodaysDate(StringBuffer line) { line.append((new Date()).toString()); }
- Declaring a Constructor as a Method
- Java object constructors look a lot like Java object methods. The only only obvious syntactic difference is the constructors' lack of a return type, and the fact that the constructors have the same name as the class. Unfortunately, Java permits normal methods to have the same name as their class as well.In the example below, the student expects the attribute list to be initialized to a fresh empty Vector object. This does not happen, because the method named 'IntList' is not a constructor.
Mistake Example public class IntList { Vector list; // This may look like a constructor, but it is a method public void IntList() { list = new Vector(); } public append(int n) { list.addElement(new Integer(n)); } }
Corrected Example public class IntList { Vector list; // This is a real constructor public IntList() { list = new Vector(); } public append(int n) { list.addElement(new Integer(n)); } }
- Forgetting to Cast Object Data Types
- Like almost all object-oriented languages, Java permits an object of a subclass type to be treated as an object of any superclass type. This is called upcasting, and in Java upcasting is automatic; it need to be explicitly specified with a cast. However, if a variable, parameter, or return value is declared with a particular superclass type, attributes and methods of subclasses will not be visible. Treating a superclass object as its real subclass is called 'downcasting', and in Java all downcasting must be explicit.Students often forget about the explicit downcast requirement. This is most common when they use Object arrays or the java.util collection classes, like Vector or Hashtable. which will store any kind of object and return type Object. In the example below, a String object is put into an array, and then retreived from the array to do a string comparison operation. The compiler will catch this and refuse to compile the code until a cast is applied.
Mistake Example Object arr[] = new Object[10]; arr[0] = "m"; arr[1] = new Character('m'); String arg = args[0]; if (arr[0].compareTo(arg) < 0) { System.out.println(arg + " comes before " + arr[0]); }
Corrected Example Object arr[] = new Object[10]; arr[0] = "m"; arr[1] = new Character('m'); String arg = args[0]; if ( ((String)arr[0]).compareTo(arg) < 0) { System.out.println(arg + " comes before " + arr[0]); }
- Extending Interfaces
- For many students, the distinction between classes and interfaces is not immediately clear. Therefore, some students will try to implement an interface like Observer or Runnable using the keyword extends instead of the correct keyword implements. The fix is to replace the keyword.
Mistake Example public class SharkSim extends Runnable { float length; ... }
Corrected Example public class SharkSim implements Runnable { float length; ... }
More Mistake Examples // Out of order public class SharkSim implements Swimmer extends Animal { float length; ... } // multiple implements keywords public class DiverSim implements Swimmer implements Runnable { int airLeft; ... }
Corrected Examples // Correct order public class SharkSim extends Animal implements Swimmer { float length; ... } // Multiple interfaces separated by commas public class DiverSim implements Swimmer, Runnable { int airLeft; ... }
- Calling Superclass Methods and Forgetting to Use the Return Value
- Java allows a subclass method to call the same method in the superclass by using the keyword super. Sometimes, a student will know that they have to call a superclass method, but won't use the return value for anything. This is very confusing for some students, usually because they haven't become comfortable with methods and return values yet.In the example below, the student wants to include the superclass toString() result in the subclass toString() result. The mistake is forgetting to capture and use the return value from super().
Mistake Example public class GraphicalRectangle extends Rectangle { Color fillColor; boolean beveled; ... public String toString() { super(); return("color=" + fillColor + ", beveled=" + beveled); } }
Corrected Example public class GraphicalRectangle extends Rectangle { Color fillColor; boolean beveled; ... public String toString() { String rectStr = super(); return(rectStr + " - " + "color=" + fillColor + ", beveled=" + beveled); } }
- Forgetting to Add AWT Components
- The Java Abstract Window Toolkit (AWT) has a simple model for building graphical user interfaces: each component of the interface must first be created by calling its constructor, then second incorporated into the GUI by calling the add() method of its parent component. In this way, an AWT interface is built up hierarchically.Students will sometimes forget this two-step process. They will create a component correctly, then forget to incorporate it into the interface. This omission will not trigger any compile-time error messages or any run-time exceptions; the components will simply not be visible.
Mistake Example public class TestFrame extends Frame implements ActionListener { public Button exit; public TestFrame() { super("Test Frame"); exit = new Button("Quit"); } }
Corrected Example public class TestFrame extends Frame implements ActionListener { public Button exit; public TestFrame() { super("Test Frame"); exit = new Button("Quit"); Panel controlPanel = new Panel(); controlPanel.add(exit); add("Center", controlPanel); exit.addActionListener(this); } public void actionPerformed(ActionEvent e) { System.exit(0); } }
- Neglecting Import Statements
- Java supports a hierarchical package structure for classes. If a class refers to another class from a different package, it can be made accessible with an import statement. Most students readily understand the notion of separate packages and import statements.Classes from the package java.lang are imported automatically into every Java program. Some students may forget that classes from other Java standard packages, such as java.net, are not imported automatically, but must be explicitly imported.
In the example below, the Canvas superclass is not defined. This mistake will be caught by the compiler, but the error message will simply point out that the class is undefined, and will not say anything about the necessity for an import statement. Similarly, the class Vector is not defined either.
Mistake Example public class MyCanvas extends Canvas { int x; Vector stuff; }
Corrected Example 1 import java.awt.*; import java.util.*; public class MyCanvas extends Canvas { int x; Vector stuff; }
Corrected Example 2 public class MyCanvas extends java.awt.Canvas { int x; java.util.Vector stuff; }
- Forgetting to Start Threads
- Java supports multi-threading with the java.lang.Thread class. The lifecycle of a thread consists of four states: initialized, running, blocked, and dead. A newly created thread is always in the initialized state; to move it to the running state, the programmer must explicitly call the thread's start() method. Sometimes a student will create a thread but forget to start it. Usually, this mistake stems from lack of experience with parallel or multi-threaded programming. The only fix is to be sure to start the thread. For simple classroom exercises, it is usually okay to simply start the thread immediately after creating it.In the example below, the student wants to do some animation using a Runnable, but has forgotten to start their thread.
Mistake Example public class AnimCanvas extends Canvas implements Runnable { protected Thread myThread; public AnimCanvas() { myThread = new Thread(this); } // the run() method never gets called because the // thread never gets started. public void run() { for(int n = 0; n < 10000; n++) { try { Thread.sleep(100); } catch (InterruptedException e) { } animateStep(n); } } ... }
Corrected Example public class AnimCanvas extends Canvas implements Runnable { static final int LIMIT = 10000; protected Thread myThread; public AnimCanvas() { myThread = new Thread(this); myThread.start(); } public void run() { for(int n = 0; n < LIMIT; n++) { try { Thread.sleep(100); } catch (InterruptedException e) { } animateStep(n); } } ... }
- Using Deprecated java.io.DataInputStream readLine()
- In Java 1.0, the correct way to read a line of text was to use the readLine() method of java.io.DataInputStream. In Java 1.1, a whole set of new IO classes were added, designed to support character and text IO: Readers and Writers. Therefore, in Java 1.1 and later, the correct way to read a line of text is to use the readLine() method of java.io.BufferedReader. Students may not know about this change, especially if they have old Java books or if the class uses out-dated examples.The old readLine() method is still part of the JDK, but the compiler flags it as deprecated. This is often very confusing to students. It is important to explain that using the old java.io.DataInputStream readLine() method is not wrong, it is just old-fashioned. Encourage the student to use BufferedReader instead.
Mistake Example public class LineReader { private DataInputStream dis; public LineReader(InputStream is) { dis = new DataInputStream(is); } public String getLine() { String ret = null; try { ret = dis.readLine(); // Warning! Deprecated! } catch (IOException ie) { } return ret; } }
Corrected Example public class LineReader { private BufferedReader br; public LineReader(InputStream is) { br = new BufferedReader(new InputStreamReader(is)); } public String getLine() { String ret = null; try { ret = br.readLine(); } catch (IOException ie) { } return ret; } }
- Assigning and passing double literals as floats
- Like almost all computer languages, Java supports floating-point (aka decimals, aka rationals) numbers. Java has two primitive types for floating-point numbers: double for 64-bit IEEE double-precision, and float for 32-bit IEEE single-precision. The confusion about this issue for novice programmers relates to decimal literals. Decimal literals, like 1.75 or 12.9e17 or -0.00003, are treated by the compiler as type double.Now, Java does not permit implicit data type casting when the operation might lose information. Such casting must be explicit. For example, Java will not allow assignment of an int literal to a byte variable without a cast, as shown in the example below.
byte byteValue1 = 17; /* WRONG! */ byte byteValue2 = (byte)19; /* OKAY */
float realValue1 = -1.7; /* WRONG! */ float realValue2 = (float)(-1.9); /* OKAY */Programmers accustomed to the forgiving data type strictures of C and C++ will often be stymied by this problem. It is understandable, and the burden sits squarely on the Java programming instructor to give his or her students clear instruction about data types and how to handle them. There are three ways to fix this particular problem.
- Use a variable of type double instead of type float. This is usually the simplest solution. In fact, there is little motivation to use floats in a Java program; any minor performance gain that a program might reap from using 32-bit math over 64-bit math will be vastly overshadowed by the JVM's overhead (Note: many modern CPUs convert all floating point numbers, of any size, into IEEE 80-bit register format before operating on them). The only real reason to use floats rather than doubles is when you are storing a lot of them, and need to save space.
- Use a literal marker to inform the Java compiler that you want your number to be treated as type float rather than type double. The marker for floats is 'f'. So, the value 1.75 is a double in Java, but the value 1.75f is a float. Here is a simple example:
float realValue1 = 1.7; /* WRONG! */ float realValue2 = 1.9f; /* OKAY */
- Use an explicit cast. This is the least elegant method, but useful when you need to convert a variable of type double into a variable of type float. Here is an example:
float realValue1 = 1.7f; double realValue2 = 1.9; realValue1 = (float)realValue2;
Compiler Problems
'javac' is not recognized as an internal or external command, operable program or batch file
If you receive this error, Windows cannot find the compiler (
javac
).Here's one way to tell Windows where to find
javac
. Suppose you installed the JDK in C:\jdk1.7.0
. At the prompt you would type the following command and press Enter:C:\jdk1.7.0\bin\javac HelloWorldApp.java
If you choose this option, you'll have to precede yourjavac
andjava
commands withC:\jdk1.7.0\bin\
each time you compile or run a program. To avoid this extra typing
Updating the PATH Environment Variable (Optional)
You can run the JDK without setting thePATH
environment variable, or you can optionally set it so that you can conveniently run the JDK executable files (javac.exe
,java.exe
,javadoc.exe
, and so forth) from any directory without having to type the full path of the command. If you do not set thePATH
variable, you need to specify the full path to the executable file every time you run it, such as:C:\> "C:\Program Files\Java\jdk1.7.0\bin\javac" MyClass.javaIt is useful to set thePATH
variable permanently so it will persist after rebooting.To set thePATH
variable permanently, add the full path of thejdk1.7.0\bin
directory to thePATH
variable. Typically, this full path looks something likeC:\Program Files\Java\jdk1.7.0\bin
. Set thePATH
variable as follows on Microsoft Windows:
Click Start, then Control Panel, then System. Click Advanced, then Environment Variables. Add the location of thebin
folder of the JDK installation for thePATH
variable in System Variables. The following is a typical value for thePATH
variable:C:\WINDOWS\system32;C:\WINDOWS;C:\Program Files\Java\jdk1.7.0\binNote:
- The PATH environment variable is a series of directories separated by semicolons (
;
) and is not case-sensitive. Microsoft Windows looks for programs in thePATH
directories in order, from left to right. - You should only have one bin directory for a JDK in the path at a time. Those following the first instance are ignored.
- If you are not sure where to add the path, add it to the right of the value of the
PATH
variable. - The new path takes effect in each new command window you open after setting the
PATH
variable.
2.Class names, 'HelloWorldApp', are only accepted if annotation processing is explicitly requested
If you receive this error, you forgot to include the .java
suffix when compiling the program. Remember, the command is javac HelloWorldApp.java
not javac HelloWorldApp
.Syntax Errors (All Platforms)
If you mistype part of a program, the compiler may issue a syntax error. The message usually displays the type of the error, the line number where the error was detected, the code on that line, and the position of the error within the code. Here's an error caused by omitting a semicolon (;
) at the end of a statement:
testing.java:14: `;' expected. System.out.println("Input has " + count + " chars.") ^ 1 error
;
) from the bold line:
while (System.in.read() != -1) count++ System.out.println("Input has " + count + " chars.");
testing.java:13: Invalid type expression. count++ ^ testing.java:14: Invalid declaration. System.out.println("Input has " + count + " chars."); ^ 2 errors
count++
, the compiler's state indicates that it's in the middle of an expression. Without the semicolon, the compiler has no way of knowing that the statement is complete.
If you see any compiler errors, then your program did not successfully compile, and the compiler did not create a .class
file. Carefully verify the program, fix any errors that you detect, and try again.
Semantic Errors
In addition to verifying that your program is syntactically correct, the compiler checks for other basic correctness. For example, the compiler warns you each time you use a variable that has not been initialized:
testing.java:13: Variable count may not have been initialized. count++ ^ testing.java:14: Variable count may not have been initialized. System.out.println("Input has " + count + " chars."); ^ 2 errors
.class
file. Fix the error and try again.Runtime Problems
Error Messages on Microsoft Windows Systems
Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorldApp
java
cannot find your bytecode file, HelloWorldApp.class
.
One of the places java
tries to find your .class
file is your current directory. So if your .class
file is in C:\java
, you should change your current directory to that. To change your directory, type the following command at the prompt and press Enter:
cd c:\java
C:\java>
. If you enter dir
at the prompt, you should see your .java
and .class
files. Now enter java HelloWorldApp
again.
If you still have problems, you might have to change your CLASSPATH variable. To see if this is necessary, try clobbering the classpath with the following command.
set CLASSPATH=
java HelloWorldApp
again. If the program works now, you'll have to change your CLASSPATH variable. To set this variable, consult the Updating the PATH variable section in the JDK 7 installation instructions. The CLASSPATH variable is set in the same manner.
Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorldApp/class
A common mistake made by beginner programmers is to try and run the java
launcher on the .class
file that was created by the compiler. For example, you'll get this error if you try to run your program with java HelloWorldApp.class
instead of java HelloWorldApp
. Remember, the argument is the name of the class that you want to use, not the filename.
Exception in thread "main" java.lang.NoSuchMethodError: main
The Java VM requires that the class you execute with it have a main
method at which to begin execution of your application.Error Recovery
Error Recovery - Introduction
Error recovery consists of :- a) Catching the errors b) Restoring the application to some stable state from which the user can continue.When programming a user interface you should spend 50% (or more) of your time on error recovery. This should include explicit and informative error messages, the suppression of non-critical errors, and the recovery from all errors.Non-Critical versus Critical Errors
Non-critical errors are ones the user can safely ignore. Critical errors require you to notify the user, either because they corrupt basic data or the program has not be able to perform a user request. For both non-critical and critical errors, you should take steps to recover from the error by restoring the application to some stable state and allow the user to continue.Non-Critical Errors
Non-Critical Errors are ones that can be safely ignored (as far as the user in concerned) and should just be logged in an error log file. For example:- If an error occurs when saving the cursor position on application exit, don't prompt the user with an error message, just log it and continue. If an error occurs on reloading the cursor position on application start-up (probably due to the error above), don't prompt the user, just log the error and continue.Not having the cursor in the last position may be a slight annoyance to the user but it does not stop them from doing useful work. It is not worthy of notifying the user every time they load a file. Of course you need to exercise some judgement about what is a non-critical error.Critical Errors
Critical Errors should be notified to the user but almost never require aborting the applicationThis is because modern applications provide the user with a number of functions, a failure of one of the functions should not require the entire application to shut down. Even anOutOfMemoryError
is not fatal. For example JEdit recovers if you run out of memory when trying to open a large file. Parallel also recovers from out of memory errors.If the error occurs as a result of a user request, such as an undo/redo request, politeness requires that you inform the user that the application could not complete the request, so this is a critical error.Checked and Unchecked Exceptions.
The standard method, in Java, of notifying the calling method that an error has occurred is by throwing a Throwable or one of its sub-classes (hereafter referred to a an exception with a small 'e').There are two basic types of exceptions[i] in Java , checked and unchecked exceptions. Throwables that are checked must be declared in a throws clause of any method that could throw them. Unchecked exceptions need not be declared. The Throwables class has two sub-classes,java.lang.Error
andjava.lang.Exception
. Alljava.lang.Error
s are unchecked and need not be declared in a throws clause. Somejava.lang.Exception
s are checked e.g.IOException
while some are unchecked e.g.IllegalArgumentException
.In the earlier versions of Java (prior to V1.4) there was a clear distinction between checked and unchecked exceptions. Checked exceptions were thrown when some error outside the program's control occurred, such as a file error, while unchecked exceptions where thrown due to some program error, such as an illegal argument. However with the introduction of the chained exception facility in Java V1.4 this general rule disappeared. One of the reasons given in the java.lang.Throwable documentation".. is that the method that throws it (the exception) must conform to a general-purpose interface that does not permit the method to throw the cause directly. For example, suppose a persistent collection conforms to the Collection interface, and that its persistence is implemented atop java.io. Suppose the internals of the put method can throw an IOException. The implementation can communicate the details of the IOException to its caller while conforming to the Collection interface by wrapping the IOException in an appropriate unchecked exception. (The specification for the persistent collection should indicate that it is capable of throwing such exceptions.)"That is, the Java class designers found they need to overcome failings in the interface specification. The result of this is that from Java V1.4 onwards you cannot rely on the distinction between unchecked and checked exceptions. This means you now have to catch all exceptions and look at the cause to see how they should be handled. The first four coding rules below cover this in more detail.Actually I would argue that even without this loss of distinction between checked and unchecked exceptions, you should have been catching all exceptions even prior to Java V1.4.So the general rule is catch and recover from all exceptions (i.e. Throwables) and only tell the user if you have to.Note: All exceptions includesjava.lang.Error
as well asjava.lang.Exception
. As noted above anOutOfMemoryError
is not necessarly fatal and anAssertionError
can also be recovered from. You should at least try and log and recover from alljava.lang.Error
exceptions. If the whole application really fails you are no worse off and often the application won't completely fail when ajava.lang.Error
is thrown.For more details on what Sun got wrong with exceptions see Unified Error/Information Messages and HelpCoding Rules for Error Recovery
Rule 1: Catch All Throwables that get to main() Rule 2: Don't let Throwables escape actionPerformed() methods Rule 3: Don't let Throwables escape listener methods Rule 4: Catch all Throwables that get to the Thread run() method Rule 5: Recover from Throwables Rule 6: Don't just Catch and Print Throwables Rule 7: Debugging code should never throw ThrowablesIf an exception ever reaches your application'smain()
method then your error recovery has failed its job. The best you can do is make sure the user can tell you where the error occurred.In order to find out what went wrong you must catch the exception in main and write the stack trace to a log file. Printing the stack trace to the terminal window is not sufficient. If your application is not attached to a terminal window then the stack trace will be lost. Even if your application is attached to a terminal window, writing the errors to a well documented log file makes it easier for the user to send you the output so you can fix the error.So yourmain()
method should always look something like this.static { // set up logging for errors LogStdStreams.initializeErrorLogging("applicationLog.log", "Log File for Application "+ new Date(), true, true); // redirect System.out as well as System.err and append to existing log } public static void main(String[] args) { try{ .... // the real code of your application goes here ... } catch (Throwable t) { System.err.println(StringUtils.toString(t)); exit(1); }The LogStdStreams class allows you to redirect System.err and System.out to a file so that in thecatch
clause above you can just saySystem.err.println(StringUtils.toString(t));
The StringUtils class shows you how to get the stack trace of an exception as a String that you can write to a file.Typical user interaction with your application will be via menu items or button actions. The associatedactionPerformed(ActionEvent e)
methods are called on the Swing event thread. (java.awt.EventDispatchThread). You should never letactionPerformed(ActionEvent e)
methods throw any exception. If theactionPerformed(ActionEvent e)
method throws an exception thejava.awt.EventDispatchThread.run()
method terminates and the Java Virtual Machine tries to print the stack trace to the terminal window (if there is one) and then exits.Note: The net result is that your program exits, but not through yourmain()
method, so Rule 1 does not help you with errors.You should catch all exceptions here and give the user a useful message telling them why their requested action was not completed successfully.YouractionPerformed(ActionEvent e)
method should always look something like this.public void actionPerformed(ActionEvent e) { try { ... // handle action here ... } catch (Throwable t) { // recover from error here Application.showErrorMessage(t); // if this action is associated with a particular window then use // Application.showErrorMessage(window,t); instead } }Here theApplication.showErrorMessage(t1);
statement handles the displaying of the message to the user and/or, as appropriate, the logging of the error to the log file.The ExampleShowErrorMessage.java.txt file contains the outline of a static application wideshowErrorMessage()
method. Unified Error/Information Messages and Help shows you a method of generating user error messages from exceptions and integrating them into the JavaHelp system.Running the ExampleShowErrorMessage application displays a dialog box like thisNote that this error message clearly tells the user what has happened and what you intend to do about it and invites the user to continue to use the application. The help button should link to more detailed help. See Why you should not use Dialog boxes to Interrupt the User for further discussion of dialog boxes.As an extension of rule 2 above, you should catch all exceptions in listener methods. Typical Java library code for calling listeners isfor (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ListDataListener.class) { if (e == null) { e = new ListDataEvent(source, ListDataEvent.CONTENTS_CHANGED, index0, index1); } ((ListDataListener)listeners[i+1]).contentsChanged(e); } }If any of the listeners throws an exception none of the following listeners is called. Since there is no guarantee of the order in which listeners are called you cannot be sure which ones are called first.So you should write all listener methods like this (usingcontentsChanged()
as an example):-public void contentsChanged(ListDataEvent e) { try { .. // handle event here ... } catch (Throwable t) { // handle errors and recover from errors here // and either log error or if important display message to the user. } }User interfaces should make liberal use of threads to keep the user interface responsive. If you don't catch the exceptions thrown in therun()
method, then the thread terminates and the Java Virtual Machine tries to print the stack trace to the terminal window (if there is one) and the application continues to run.You should catch all exceptions that get to the run() method and take appropriate action. This action may be giving the user a useful message telling them what happened, or more commonly you will need to pass this error information back to the application so that it can take recovery action and inform the user.Programming with Java Threads provides a complete package for starting, stopping and handling errors in Java Threads. It shows you how to pass these errors back.At the very least, your Thread'srun()
method code should include the following.public void run() { try { ... // do thread work here ... } catch (Throwable t) { // do error recovery here Application.showErrorMessage(t); // or log the error if not critical to the user. } }While rules 1 to 4 above are designed to catch all the errors, this rule is about recovering from them.As mentioned above, Error Recovery is about "restoring the application to some stable state from which the user can continue". Depending on the application and the error this may be simple or complicated. Basically it involves:- a) Recovering resources left over after the error b) Restoring the application to some stable state. Ideally this will be the state it was in prior to the action that caused the error.Both of these actions can often be carried out in thefinally
clause of atry/catch/finally
block. If at all possible you should clean up at the bottom of the method that allocated the resources or changed the state, as this makes for cleaner procedural programming.For example theactionPerformed()
method of the File, SaveAs menu item should look something like this// action for File SaveAs menu item public void actionPerformed(ActionEvent e) { String saveFileName = null; // mark as not collected yet FileOutputStream fos = null; // mark resource as not allocated yet try { // pick up the saveFileName from user here via FileChooser dialog fos = new FileOutputStream(saveFileName); // save to fos here ... // close output file fos.close(); fos = null; // mark as closed (resource released) } catch (IOException ex) { // handle error here in this case rethrow it with a better message // See "Unified error messages" for a better way to handle this exception throw new IOException("Error saving file to "+saveFileName+"\n"+ex.getMessage()); } finally { // always release resources i.e. close the files try { if (fos != null) { fos.close(); } } catch (IOException ex1) { // ignore this error as if fos != null we already are handling an error } // do other clean up here } }In a similar way, the application state should be saved at the top of the method and then restored if an error occurs.Inexperienced programmers will often write code like the following in low level utility methodsprivate Icon makeIcon(final String gifFile) throws IOException { InputStream resource = HTMLEditorKit.getResourceAsStream(gifFile); if (resource == null) { System.err.println(ImageView.class.getName() + "/" + gifFile + " not found."); return null; } ...... // rest of method here .... }Here the error is that thegifFile
could not be found. There are two problems with printing to System.err.:- 1) If the application is not attached to a terminal window, the output of System.err gets lost (see LogStdStreams.java.txt for a solution to this). 2) Printing an error message toSystem.err
and returningnull
limits your options for error reporting, error recovery and displaying user help.A better approach is to writeprivate Icon makeIcon(final String gifFile) throws IOException { InputStream resource = HTMLEditorKit.getResourceAsStream(gifFile); if (resource == null) { throw new FileNotFoundException(ImageView.class.getName() + "/" + gifFile); } ...... // rest of method here .... }This then lets the calling method know exactly what went wrong and give it the chance to take appropriate action, for example using a default icon or displaying a detailed error message with a connected help topic.This example is actually from the javax.swing.text.html.ImageView class. There are many places in the Java library source were errors are "handled" by just writing a message toSystem.err
and in some cases dumping the stack trace. Some of these are just bad programming and hopefully will disappear over time. However some are due to the lack of a built in method of passing back exceptions from threads.For your own threads you can use the thread package described in Programming with Java Threads to handle exceptions in threads but for the Java library'sSystem.err.println()
statements you will need to use LogStdStreams.java.txt to at least insure the errors are logged.Another version of this problem ispublic void doSomeThing(String input){ ... try { // call some method here that throws a checked exception such as a BackingStoreException preferences.flush(); // this method, doSomeThing, does not throw BackingStoreException so the temptation is to just catch an ignore } catch {BackingStoreException ex) { // don't know what to do so ignore } }In this case the BackingStoreException should be wrapped in an unchecked exception such as a RuntimeException, using Java V1.4 exception chaining, ie.public void doSomeThing(String input){ ... try { // call some method here that throws a checked exception such as a BackingStoreException preferences.flush(); } catch {BackingStoreException ex) { throw new RuntimeException("Error flushing preferences",ex); } }Then you have to catch all exceptions and check their causes to see what really happened.Bruce Eckel also mentions this temptation to “swallow exceptions” in his discussion on CheckedExceptions . He argues that checked exceptions actually encourage programmers to write code that catches and ignores exceptions.Debugging code is supposed to assist you in checking the operation of your application and in finding and correcting errors. It is not much use if it itself throws exceptions.The most common problem is in a class'stoString()
method. When an error occurs in the class, some of its member objects may be left in an invalid state (often null). TypicaltoString()
methods try and print out the value of the class's members usingobject.toString()
.Obviously this will throw aNullPointerException
if theobject
isnull
. The preferred way of converting member objects to strings for printing is to use the StringUtils.toString() methodStringUtils.toString(object)
This method checks fornull
objects and then callsobject.toString()
and catches and returns as a String any exceptions thrown that method might throw.
No comments:
Post a Comment