Debugging Code
Debugging Code
Preface
As you start working with Object Oriented Programming, you’ll encounter multiple files, classes, inheritance, and
other components that make debugging more challenging. No longer will you be working with all your code in a
single file. As such, we’ve put together this debugging guide that illustrates some common errors and how to debug.
List of Common Errors
There are quite a few errors in Java that you will regularly encounter.
Compile-Time Errors
These errors will occur before your program actually runs. These errors are thrown by the Java compiler:
something expected The parser was surprised by a symbol you wrote at or just before this point.
cannot find symbol class Make sure that file is saved in the same folder as the file that refers to it.
cannot find symbol method You got the method name wrong, or you called it on the wrong file/class.
class, interface, or enum expected You have too many closing braces.
illegal start of expression You're missing a closing brace for the previous method declaration.
incompatible types - expected type Make sure you understand why it found what it did, and why it expected what it did.
The compiler found a pathway through your non-void method that does not reach
missing return statement
a return statement.
reached end of file while parsing You're missing a closing brace at the end of the file.
The compiler found a pathway through your method where you access the value of a
variable might not have been initialized
variable before you've assigned anything to it.
Run-Time Errors
These errors occur during the lifetime of your application or program. They may occur when clicking a button,
providing a certain input, or during regular execution. We’ve included some sample code to demonstrate a few of
these common runtime errors later in this document.
My program freezes You have a loop that never reaches its stopping condition.
ArrayIndexOutOfBoundsException You tried to access an array element with an index that was too high or too low.
NullPointerException You tried to call a method on null (or access an array element of null).
StringIndexOutOfBoundsException You tried to access a character in a String with an index that was too high or too low.
IndexOutOfBoundsException
The IndexOutOfBoundsException occurs when trying to access an index larger than the last index for an object. This
occurs most frequently with Arrays (primitive), ArrayLists, and Strings, but it can occur almost anywhere. The
easiest way to debug this is using print statements. The following code throws an IndexOutOfBoundsException:
How can we debug this error? The easiest way is using System.out.println() statements. For this method, let’s
print out each number (i) that we are checking. Adapting the method:
0
1
2
3
4
5
(error) IndexOutOfBoundsException
This means we are checking indices 0-5. However, we only have indices 0-4, since our int[] is of size 5. In this case,
our for loop is iterating one too many times. We need to change the <= to < and our code works! Here’s the final
solution:
Note that you should always remove debugging statements from your final submission. This is discussed in more
detail in the Style Guide.
NullPointerException
The NullPointerException occurs when you try to call a method on a null object. A null object is an object that
doesn’t exist… it is “nothingness.” This occurs most frequently with LinkedLists and BinaryTrees, but you could
encounter it anywhere. The easiest way to debug this error is with System.out.println() statements. The following
code throws a NullPointerException:
String[] arr = {‘The’, ‘quick’, ‘brown’, ‘fox’, ‘jumped’, ‘over’, ‘the’, null};
the
quick
brown
fox
jumped
over
the
(error) NullPointerException
This is because we are trying to call the method toLowerCase() on a null object. null objects do not have methods,
so this breaks. In order to fix this code, we need to first check if the object is not null before calling toLowerCase():
String[] arr = {‘The’, ‘quick’, ‘brown’, ‘fox’, ‘jumped’, ‘over’, ‘the’, null};
the
quick
brown
fox
jumped
over
the
Note that, when checking if objects are null, we use == and != instead of the equals() or !equals() methods. This
is because you cannot call methods on a null object.
Logic Errors
These are the most detrimental of all errors, because you don’t know they exist. They don’t (necessarily) cause the
program to crash, but rather produce invalid output. Writing unit tests for your classes most frequently catches
these errors.
When you pass a primitive data type, such as a char, int, float, double, or boolean, to a function, you are passing
by value. That means that a copy of the data type is duplicated, and passed to the function. If the function chooses
to modify that value, it will be modifying the copy only. Once the function finishes, and control is returned to the
returning function, the "real" variable will be untouched, and no changes will have been saved. If you need to
modify a primitive data type, make it a return value for a function, or wrap it inside an object.
When you pass a Java object, such as an Array, a Vector, or a String, to a function then you are passing by
reference. Yes – a String is actually an object, not a primitive data type. So that means that if you pass an object to a
function, you are passing a reference to it, not a duplicate. Any changes you make to the object's member variables
will be permanent – which can be either good or bad, depending on whether this was what you intended.
Comparison Assignment
This is an easy error to make. If you're used other languages before, such as Pascal, you'll realize just how poor a
choice this was by the language's designers. In Pascal, for example, we use the := operator for assignment, and
leave = for comparison.
Fortunately, even if you don't spot this one by looking at code on the screen, your compiler will. Most commonly, it
will report an error message like this: "Can't convert something to boolean", where something is a Java type that
you're assigning instead of comparing.
// Bad way
if (front == back) { … }
// Good way
if(front.equals(back)) { … }
If you execute this code, it will run fine. However, you’ll get a count of 0, no matter what word you put it! Examining
the code, you might begin to add System.out.println() statements at various steps. If we add the following
debugging code:
You’ll will notice that the expression “s” == “s” is always false. You quickly recall that the Java == method objects,
not object contents. You must use the equals() method:
You run your code against the word “go” and it returns 1. Yay! You turn in your homework and get no credit for this
method. Why? You still have additional logic errors. Running a test suite with more words will demonstrate why:
so 1
hit 0
soso 1
fist 0
sift 1
You can see that it’s only checking the first letter of the word. It seems to be ignoring every other word. Jumping
back into our code, you can see that we are always looking at the same index in our substring() method:
input.substring(1,1).equals(“s”)
input.substring(i,i+1).equals(“s”)
Everything works as expected! Just kidding, we still have logic errors in our code. Expanding our unit tests to the
following set of words, we get the following output:
so 1
hit 0
soso 1
fist 0
sift 1
Seth 0
seaShells 1
Immediately you should notice that the method is ignoring uppercase letters in it’s counting. Digging through the
Java API, you realize there are three routes you can take to alleviate this problem:
1. Document your method, indicating that it only returns lowercase letter counts
2. Convert the incoming input to all lowercase or uppercase before comparing using the equals() method
3. Use the Java String‘s equalsIgnoreCase() method
1. You are restricting your program significantly. Eventually you will probably have to write a complementary
method that checks for all capitalized versions of the letter anyway. This is just bad practice. Furthermore,
you run the risk of programmers not reading your documentation. It’s unfortunate, but this is often the case.
2. Convert the incoming input to all lowercase or uppercase before comparing using the equals() method.
This seems logical and will work for the purpose of the method. However, in terms of efficiency, it’s
compares poorly to option 3. Why? The method toLowerCase() iterates over every character in the String
and converts to it’s lowercase equivalent. Then our method is iterating over each character in the String
and comparing it to the lowercase letter “s”. You are doing twice the work!
The moral of this story: You should always write very exhaustive unit tests – sometimes before you even start
coding. There’s a PDF on Unit Testing for reference and a quick start guide on the course website.
Reading a Stack Trace
At some point, unless you are the best programmer in the world, you will see something like this:
This is called a stack trace. It means that the Java runtime encountered an error. Stack traces range in length from a
single line to hundreds of lines of code. They provide a full trace of where java thinks the problem happened. Each
line will have certain properties – the method name, the line the method occurs on, the file name, the line number:
The same is true for students who are not currently enrolled in the course (such as students who have previously
taken or been a TA for this course). There are only 3 people who are authorized to look at your code:
1. You
*
2. Any TA or CA currently employed by the course
†
3. Any Professors of the course
*
The official list of TAs and CAs is available on the course website
†
The official list of Professors is available on the course website