Run-time Errors

[ Previous: Compile-time Errors | Return to Main Page | Next: Intent Errors ]

Once your code is able to compile and is executed, you will often get errors called "run-time" errors. These errors (also called "exceptions") halt the execution of your program. There are fewer possible run-time errors than there are compile-time errors, but the procedure for debugging them is not always clear. This page suggests a general method of debugging any run-time errors you encounter.

Sections on this page (click to jump to that section):

  1. Important Points to Keep in Mind
  2. How to Debug Your Code
  3. Examples: Null Pointer Exceptions
  4. Example: Array Index Out of Bounds Exceptions

Important Points to Keep in Mind

Debugging run-time errors in your code can be an extremely time-consuming process. However, there are a few points that you should first keep in mind when attempting to find where the problem is in your code.

  1. Java executes code in a strict order.

    This fact is probably something that is taught to you the very first time you see Java code, but it is important to keep in mind when debugging. The only time the flow of code execution will "jump" to another place is when you instruct it to do so. Things such as using a loop, an if statement, or calling another method will change which line of code is to be executed next, rather than Java executing the next line of code by default.

    For example, consider the following code:

    01  this.numPeople++;
    02  this.numRegistered++;
    03  int age = this.person.getAge();
    04  if (age >= 16)
    05  {   this.person.setDrive(true);
    06  } else
    07  {   this.person.setDrive(false);
    08  }
    09  //...
    

    The important thing to recognize here is that Java will call lines 1 through 3 in that order, but will not call line 4 directly after line 3. Rather, because line 3 calls another method, Java will call the first line of the getAge() method directly after reaching line 3 in the above code. When the getAge() method finishes, then execution will be returned to this code and line 4 will be executed.

    Similarly, when Java evaluates the if clause at line 4, either line 5 or line 7 will be executed next, followed by line 9. Recognizing the order in which lines of code are executed will be very valuable in debugging.

  2. A single line of code stopped the execution of your program.

    When a run-time error occurs, the Java compiler will tell you the last line which was executed before the program crashed. That is, the last line executed is the one line (and only line) that is responsible for causing the run-time error to occur.

  3. The line containing the error may not need changing.

    Although the execution of one specific line is what caused your program to stop running, this line is not necessarily incorrect. For example, say we get a null pointer exception on the following line in our code:

    String personName = thePerson.getName();
    

    We may very well want to call the getName() method on thePerson and then store its resulting String into a local variable called personName, so there is nothing actually wrong with this line itself. However, this line may have given us a null pointer exception because we have forgotten to initialize thePerson elsewhere in our code, and so trying to call the getName() method on this null object will cause an error.

    To summarize, the specific line that is the cause of the error may not necessarily be the reason for the error. Rather, a problem with code elsewhere in your program could need fixing instead.

  4. Values at runtime aren't always what you think they are.

    Making assumptions about which values certain variables hold during the execution of your program can lead to several problems. For instance, consider the following method:

    01  public double getCourseAverage()
    02  {   
    03      int total = 0;
    04      for (int i=0; i<this.grades.length; i++)
    05      {
    06          total += this.grades[i];
    07      }
    08
    09      int n = this.getNumberOfStudents();
    10      double avg = total / n;
    11     
    12      return avg;
    13  }
    

    When we look at this method, we expect that it will work because we assume that this.grades holds all of the students' correct marks and we also assume that the method getNumberOfStudents() will correctly return the number of students in the course. However, upon running the program, we may find that the getNumberOfStudents() method returns 0, causing a "Division by Zero" error. Or we may find that the this.grades array has not been filled with values, has been filled incorrectly, or has a length greater than what we expected.

    In short, don't assume anything about your code during runtime, no matter how trivial. Often, the thing you don't expect may actually be the reason for the error.

The next section outlines steps you should take in order to find bugs in your code which are the source of run-time errors.


How to Debug Your Code

Reading the previous section, it would seem that there may be errors anywhere in your code, even where you expect them the least, and that they are very hard to find. This is partially true: errors can be in many different places and can take a lot of time to fix, but fortunately there is a very logical way of going about doing this.

Keeping the above facts in mind, the following lays out a series of three important steps you should take every time you get a run-time error. Following these steps, you will always be able to find the bugs in your code (even though some may take a lot longer than others).

  1. Look at the line number and type of error.

    As mentioned in the previous section, one line is going to be the reason why your program stopped running. Different development environments (e.g. Dr. Java, JBuilder, JCreator) will most likely report errors in a slightly different format, but the the error messages you receive in the console output will all contain the same basic information:

    You should pay special attention to this information and use it as a "starting point" for debugging your code. The following example shows a sample error message.

    Code Console Output

    This code's main method is executed.

    Upon execution, we receive this message in the console window.

    Line (a) tells us the type of the error: a null pointer exception.

    Then line (b) tells us which line in the code caused the error to occur and in which class (in this case, line 7 in the Test class is to blame).

    Finally, line (c) tells us that line 14 in our code was the last method call made (to the Test constructor) which led us to line 7.

    01  public class Test
    02  {
    03      private Person p;
    04  
    05      public Test()
    06      {
    07          String personName = p.getName();
    08          int nameLength = personName.length();
    09          System.out.println(nameLength);
    10      }
    11  
    12      public static void main(String[] args)
    13      {
    14          Test t = new Test();
    15      }
    16  }
    
    
    (a)  java.lang.NullPointerException: 
    (b)    at Test.(Test.java:7)
    (c)    at Test.main(Test.java:14)
    (d)    at java.lang.reflect.Method.invoke(Native Method)
    
    

    Using this information, we can "trace through" the order of execution in our code which will lead us to the error.

  2. Think critically about your code.

    Using the error message we receive from the console, the best thing to do is start at the line which caused the error and trace backwards, asking yourself questions about your code as you go along. As a simple example, consider what you may ask yourself/figure out about the code example used in step 1:

    This approach has solved the problem. After going to line 3 and fixing the problem by initializing the variable to refer to an object in memory, you should re-compile and run your code again to ensure that things work. You may run into more errors, but at least this one has been fixed.

    Certainly, the types of questions you ask yourself will change with your programming experience. This works for some errors, but not for all of them. If your code is too complicated for you to just be able to "see" the problem (and this is usually the case), then the next step will certainly help you out.

  3. If the solution is not obvious, systematically isolate the problem.

    If you can't immediately "see" the problem, then don't be afraid to use the console to help you keep track of values at runtime. That is, Use System.out.println() to help you debug your code. Alternatively, environments will usually come with built-in debugging tools which may help you debug in a more efficient manner. Feel free to learn how to use these tools and use them where you would find them helpful. But, for this guide, I use System.out.println() in order to see certain values during runtime and to see which lines of code are being executed.

    The example below is identical to the one from step 1, except I have included output to the console (in bold).

    Code Console Output

    We execute this code's main method and have included some messages to the console along the way.

    The output to the console from running this code. We should take note of the following things:

    • The fact that lines (a), (b), and (c) appear in the console window mean that lines 17, 7, and 8 were executed, respectively.
    • Similarly, because of the lack of output to the console, we know that lines 10 and 19 were not executed.
    • The output of line (c), which was from line 8 in our code, confirms the fact that p was never initialized. If p was initialized (e.g. it was non-null), then the output to the console would instead be something like p = Person@5e17f4 (this means "p refers to a Person object located at memory address 5e17f4").
    01  public class Test
    02  {
    03    private Person p;
    04    
    05    public Test()
    06    {
    07      System.out.println("Inside Test constructor");
    08      System.out.println("p = " + p);
    09      String personName = p.getName();
    10      System.out.println("personName = " + personName);
    11      int nameLength = personName.length();
    12      System.out.println(nameLength);
    13    }
    14  
    15    public static void main(String[] args)
    16    {
    17      System.out.println("Creating a Test object");
    18      Test t = new Test();
    19      System.out.println("Done program");
    20    }
    21  }
    
    
    (a)  Creating a Test object
    (b)  Inside Test constructor
    (c)  p = null
    (d)  java.lang.NullPointerException: 
    (e)    at Test.(Test.java:9)
    (f)    at Test.main(Test.java:18)
    (g)    at java.lang.reflect.Method.invoke(Native Method)
    
    

    As you can see, printing out our own messages to the console would have revealed the source of the problem to us immediately. For more complex and involved code with several different classes/methods interacting with one another, visually displaying this information is very useful.

    In general, consider including System.out.println() statements in the following helpful places:

    1. Just before the line which causes the error in order to look at the values of any variables or the return values of any method calls.
    2. At the beginnings of methods to check whether or not they are being called.
    3. At the ends of methods/code blocks to ensure that the execution makes it all the way through.
    4. Inside of loops and if statements to ensure that they are being entered (or are repeating an appropriate number of times).
    5. After initializing variables to ensure they are initialized to what you expect.
    6. Anywhere else you feel would be helpful in the context of the error (e.g. never be afraid to include more output than necessary).

Listed below are examples of applying this deductive approach to two of the most common run-time errors (or "exceptions") you will see at this level: 'null pointer exceptions' and 'array index out of bounds exceptions'.


Examples: Null Pointer Exceptions

A Null Pointer Exception is a run-time error which occurs when a method call is made on an object whose value is null. The strategy for debugging these errors is the same as described in the previous section (identify the line, consider the problem, then isolate the problem using output to the console).

For the following examples, assume we are working with a blackjack program which follows this UML diagram:

Below are two examples of common scenarios you will most likely encounter involving null pointer exceptions.

Example 1: Code Example 1: Original Console Output

Part of the BlackJack class, which is part of a much larger program.

Here, we see that we have a null pointer exception at line 15.

01  public class BlackJack
02  {
03     /** many instance variables and
04         methods have been left out */     
05
06     private Player[] players;
07     private int numPlayers;
08     private Deck deck;
09     
10     public void hitPlayer(int p)
11     {
12       Card next = this.deck.dealCard();
13         
14       int v1 = next.getValue();
15       int v2 = this.players[p].getHand()[0].getValue();
16       int numCards = this.players[p].getHand().length;
17         
18       //check for a blackjack
19       if ((numCards == 2) && (v1 + v2 == 21))
20       {  this.players[p].incrementRoundsWon();
21       } 
22     }
23  }

(a)  java.lang.NullPointerException: 
(b)    at BlackJack.(BlackJack.java:15)
(c)    [several other lines may be here]
(d)    at java.lang.reflect.Method.invoke(Native Method)

The important thing to note is that we have two possibilities for objects which are null on this line:

We should now include some output to the console to see which case it is (it would be impossible to tell from just looking at this code alone). The lines in bold have been added.

Example 1: Modified Code Example 1: New Console Output

We add in System.out.println() statements to check which object has a value of null in order to figure out how to debug our code.

From the console output, we see that this.players[p] refers to a valid object, but this.players[p].getHand()[0] is null. Therefore the null pointer exception was caused by calling the getValue() method on this null object.

01 public class BlackJack
02 {
03  /** many instance variables and
04      methods have been left out */     
05
06  private Player[] players;
07  private int numPlayers;
08  private Deck deck;
09  
10  public void hitPlayer(int p)
11  {
12   Card next = this.deck.dealCard();         
13     
14   int v1 = next.getValue();
15   System.out.println("first val: " + v1);
16   System.out.println("x: " + this.players[p]);
17   System.out.println("y: " + this.players[p].getHand()[0]);
18   int v2 = this.players[p].getHand()[0].getValue();
19   System.out.println("should not reach this line");
20   int numCards = this.players[p].getHand().length;
21       
22   //check for a blackjack
23   if ((numCards == 2) && (v1 + v2 == 21))
24   {  this.players[p].incrementRoundsWon();
25   } 
26  }
27 }

(a)  first val: 7
(b)  x: Player@5e17f4
(c)  y: null
(d)  java.lang.NullPointerException: 
(e)    at BlackJack.(BlackJack.java:18)
(f)    [several other lines may be here]
(g)    at java.lang.reflect.Method.invoke(Native Method)

Now, with the knowledge that this.players[p].getHand()[0] is null, we should check that the getHand() method is returning an array correctly. If it is, we would then want to investigate why the first element of this array is null (perhaps we skip over element 0 when we are initializing the array).

One other example of a null pointer exception involving arrays is below.

Example 2: Code Example 2: Original Console Output

Another section of code from the blackjack program.

We have a null pointer exception at line 18. This means that this.cards[a] has a value of null.

01  public class CardList
02  {
03    /** many methods and instance
04        variables left out */
05
06    private Card[] cards;
07    private int numCards;
08
09    public void shuffle()
10    {
11      int numBlack = 0;
12      int numRed = 0;
13      while (numBlack < 1000 && numRed < 1000)
14      {
15        int a = (int)(Math.random() * 52);
16        int b = (int)(Math.random() * 52);
17       
18        if (this.cards[a].getSuit() == 1
19         || this.cards[a].getSuit() == 2)
20        {  numBlack++;
21        } else
22        {  numRed++;
23        }
24
25        this.swap(a, b);
26      }
27    }
28  }

(a)  java.lang.NullPointerException: 
(b)    at CardList.(CardList.java:18)
(c)    [several other lines may be here]
(d)    at java.lang.reflect.Method.invoke(Native Method)

When working with arrays, a good strategy is to print out each array element, one per line, in order to see what the array actually holds at runtime. We do this below.

Example 2: Modified Code Example 2: New Console Output

The same code, but we print out each value within the cards array before we attempt to shuffle in order to see the array's contents.

By looking at the console output in this case, we see that the first element of the array holds the Ace of Spades, but the remaining 51 spots in the array are null.

01  public class CardList
02  {
03    /** many methods and instance
04        variables left out */
05
06    private Card[] cards;
07    private int numCards;
08
09    public void shuffle()
10    {
11      //for debugging purposes only
12      for (int i=0; i<this.cards.length; i++)
13      {  
14        System.out.println(i);
15        System.out.println(this.cards[i]);
16      }
17
18      int numBlack = 0;
19      int numRed = 0;
20      while (numBlack < 1000 && numRed < 1000)
21      {
22        int a = (int)(Math.random() * 52);
23        int b = (int)(Math.random() * 52);
24       
25        if (this.cards[a].getSuit() == 1
26         || this.cards[a].getSuit() == 2)
27        {  numBlack++;
28        } else
29        {  numRed++;
30        }
31
32        this.swap(a, b);
33      }
34    }
35  }

(a)  0
(b)  Ace of Spades
(c)  1
(d)  null
(e)  2
(f)  null
(g)  3
(h)  null
(i)  4
(j)  null
(k)  5
(l)  null
(m)  6
(n)  null
(o)  [...and so on]
(p)  java.lang.NullPointerException: 
(q)    at CardList.(CardList.java:25)
(r)    [several other lines may be here]
(s)    at java.lang.reflect.Method.invoke(Native Method)

In this case, our next step in debugging would be to examine the section of code where we initialize (fill) the array with Card objects to see why all but one element of our cards array is null.


Example: Array Index Out of Bounds Exceptions

An Array Index Out of Bounds Exception occurs when you attempt to access an array using either a negative index or an index which is at least one greater than its length.

Below is a typical example of how to debug this type of error (assume we are still using the blackjack design described in the previous section).

Example 1: Code Example 1: Original Console Output

A section of the CardList class.

We have an array index out of bounds error at line 18.

01  public class CardList
02  {
03    /** many methods and instance
04        variables left out */
05
06    private Card[] cards;
07    private int numCards;
08
09    public void shuffle()
10    {
11      int numBlack = 0;
12      int numRed = 0;
13      while (numBlack < 1000 && numRed < 1000)
14      {
15        int a = (int)(Math.random() * numCards);
16        int b = (int)(Math.random() * numCards);
17       
18        if (this.cards[a].getSuit() == 1
19         || this.cards[a].getSuit() == 2)
20        {  numBlack++;
21        } else
22        {  numRed++;
23        }
24
25        this.swap(a, b);
26      }
27    }
28  }

(a)  java.lang.ArrayIndexOutOfBoundsException: 
(b)    at CardList.(CardList.java:18)
(c)    [several other lines may be here]
(d)    at java.lang.reflect.Method.invoke(Native Method)

To debug this error, we print out all values which could be related to the problem.

Example 1: Modified Code Example 1: New Console Output

Attepting to find the problem, we print out values which are important when working with arrays:

  • The array length
  • The auxiliary variable (if the array is partially-filled)
  • The value of the indicies we are trying to access

We see that the length of the array is what it should be, but the value of numCards is 3 more than it should be. As a consequence, the randomly-generated integer a is outside of the array's upper bound, therefore causing an error.

01  public class CardList
02  {
03    /** many methods and instance
04        variables left out */
05
06    private Card[] cards;
07    private int numCards;
08
09    public void shuffle()
10    {
11      System.out.println("len: " + cards.length);
12      System.out.println("num: " + numCards);
13
14      int numBlack = 0;
15      int numRed = 0;
16      while (numBlack < 1000 && numRed < 1000)
17      {
18        int a = (int)(Math.random() * numCards);
19        int b = (int)(Math.random() * numCards);
20       
21        System.out.println("a: " + a); 
22        System.out.println("b: " + b);
23
24        if (this.cards[a].getSuit() == 1
25         || this.cards[a].getSuit() == 2)
26        {  numBlack++;
27        } else
28        {  numRed++;
29        }
30
31        this.swap(a, b);
32      }
33    }
34  }

(a)  len: 52
(b)  num: 55
(c)  a: 53
(d)  b: 6
(e)  java.lang.ArrayIndexOutOfBoundsException: 
(f)    at CardList.(CardList.java:24)
(g)    [several other lines may be here]
(h)    at java.lang.reflect.Method.invoke(Native Method)

So, our next step in debugging would be to check that we are only incrementing the value of numCards where we should be.


Return to Main Page
Created by Terry Anderson (tanderso at uwaterloo dot ca) for use by ISG, Spring 2005