Errors

tutorial java

By now, you’ve probably seen a few errors, either when compiling or running your code. They can be frustrating, but they can also give you a lot of information about exactly how you can fix the problems in your code. This tutorial covers the types of errors you’ll see when programming in Java, and how to fix them.

Compiler Errors

Remember that the compiler converts from Java code (which humans can read) to Java byte code (which computers can read). The Java compiler is a lot like a translater. Now imagine that you told a translator to translate “flibberflabby” from English to Spanish. The translator would tell you that “flibberflabby” isn’t a word, so it can’t be translated!

That’s what happens when you write code that the compiler doesn’t understand. It doesn’t know how to translate your code into Java byte code, so it gives you an error. The rules of a language are called its syntax. Compiler errors are also called syntax errors, because it means that your code broke the rules of the langauge.

Compiler errors can be for things like forgotten semicolons or misspelled variables, but they can also be for violating the rules of Java, like using a non-static variable from a static function.

Here’s an example that contains a syntax error. Can you spot it?

class BadExample(){
	public static void main(String[] args){
		System.out.println("Happy Coding!");
	}
}

Do you see the syntax error? Try to compile this code. Save it to a file named BadExample.java, and then open a command prompt to the directory that contains that file, then type javac BadExample.java and press enter.

The compiler will show you this error:

> javac BadExample.java

BadExample.java:1: error: '{' expected
class BadExample(){
                ^
1 error

This might seem like gibberish, but there’s a ton of information packed in here:

So, this error is telling us that the compiler thought it would see a curly bracket {, but it saw a parenthesis ( instead. This is a violation of the syntax for creating a class, which shouldn’t have () parentheses in the class declaration!

Now we know enough to fix our code:

class BadExample{
	public static void main(String[] args){
		System.out.println("Happy Coding!");
	}
}

Notice that we got rid of the parentheses () in the first line. This code will compile and run just fine.

It’s also possible to have more than one compiler error at a time. When this happens, always start with the first error. Fix that, and then try to compile again. Sometimes one syntax error can lead to multiple compiler errors. You should also make sure that you’ve split your problem up and you’re working on one small step. You don’t want to write 100 lines of code before compiling. Write a couple lines of code at a time, and compile as often as possible so you find errors as soon as you make them.

Runtime Errors

Even if you don’t get any compiler errors and the compiler translates your .java file into a .class file that you can run, your code still might contain other errors that the compiler can’t detect. Here’s an example:

public class ArgsPrinter{
	public static void main(String[] args){
		System.out.println(args[99]);
	}
}

This code prints the 100th argument from the args array (remember arrays are 0 based, so index 99 is the 100th index). It will compile fine, because the compiler has no way of knowing how many arguments you’re going to give your program until you actually run it.

But what happens if you run it but only give it 3 arguments?

> java ArgsPrinter
>java ArgsPrinter one two three
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 99
        at ArgsPrinter.main(ArgsPrinter.java:3)

You get a runtime error, which means that even though your code “looks” okay, it did something bad when it actually ran. Let’s look at the error message in more detail:

In this case the error was caused by accessing an index that the array didn’t have. We only gave the program 3 arguments, so the index only had 3 indexes. Then we tried to access index 99, which caused an error.

Stack Traces

Let’s look at another example that throws a runtime exception. Can you spot the error before running the code?

import java.util.ArrayList;

public class RuntimeErrorExample{

	private ArrayList<String> list = new ArrayList<String>();
	
	public void printLastThing(){
		int lastIndex = list.size() - 1;
		String thing = list.get(lastIndex);
		System.out.println(thing);
	}
	
	public static void main(String[] args){
		RuntimeErrorExample example = new RuntimeErrorExample();
		example.printLastThing();
	}
}

This code defines a RuntimeErrorExample class that contains one ArrayList variable named list. It also defines a function named printLastThing() that prints the last item in the ArrayList. The logic is correct: like arrays, ArrayList objects are 0 based, so if an ArrayList contains 5 items, the last item is at index 4. So getting its size and subtrating 1 will give us the last index.

The main() function then creates an instance of the class and calls the printLastThing() function. But when we run it, we get this runtime error:

>java RuntimeErrorExample
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
        at java.util.ArrayList.elementData(ArrayList.java:418)
        at java.util.ArrayList.get(ArrayList.java:431)
        at RuntimeErrorExample.printLastThing(RuntimeErrorExample.java:9)
        at RuntimeErrorExample.main(RuntimeErrorExample.java:15)

We’re getting an ArrayIndexOutOfBoundsException just like before, and we can see that we’re trying to access index -1, which causes an error because the index can’t be negative.

Notice that the stack trace now contains 4 lines. You might see that the error is actually happening inside the ArrayList class. Before you assume you found a bug in the ArrayList class, let’s take a closer look at the rest of the stack trace!

The stack trace shows you the sequence of events that lead to to the error happening. To make sense of a stack trace, read it from the bottom to the top:

So, even though the error is coming from inside the ArrayList class, we can use the stack trace to work backwards and find out where in our code the cause of the error came from.

In this example, the error is happening because we never actually add anything to the ArrayList, so its size is 0. Then we subtract 1, which gives us -1, and we use that as an index. That causes the error.

Catching Exceptions

We could prevent the ArrayIndexOutOfBoundsException by using an if statement that checks to make sure the index is valid before using it. Or we could use the isEmpty() function that ArrayList gives us:

import java.util.ArrayList;

public class RuntimeErrorExample{

	private ArrayList<String> list = new ArrayList<String>();
	
	public void printLastThing(){
		if(list.isEmpty()){
			System.out.println("You forgot to add something to the ArrayList.");
		}
		else{
			int lastIndex = list.size() - 1;
			String thing = list.get(lastIndex);
			System.out.println(thing);
		}
	}
	
	public static void main(String[] args){
		RuntimeErrorExample example = new RuntimeErrorExample();
		example.printLastThing();
	}
}

Now when we call the printLastThing() function, we first check if the list is empty, and print a friendly message if it is. Then only if it’s not empty, we get the last index and print out the last item.

It’s always a good idea to idiot-proof your code like this.

Another way to protect your code from exceptions is using try and catch blocks. You put code that might cause an error inside a try block, and you put code you want to happen in case of an error inside a catch block. It looks like this:

try{
	//dangerous code here
}
//exception that might happen here
catch(Exception e){
	//code you want to run when an error happens
}

Here’s the above example again, using a try and catch block instead of an if statement:

import java.util.ArrayList;

public class RuntimeErrorExample{

	private ArrayList<String> list = new ArrayList<String>();
	
	public void printLastThing(){
		
		try{
			int lastIndex = list.size() - 1;
			
			//this line throws an exception
			String thing = list.get(lastIndex); 
			
			//this line won't be called!
			System.out.println(thing);
		}
		catch(ArrayIndexOutOfBoundsException e){
			//the program "shortcuts" to this code when an exception happens
			System.out.println("You forgot to add something to the ArrayList.");
		}
	}
	
	public static void main(String[] args){
		RuntimeErrorExample example = new RuntimeErrorExample();
		example.printLastThing();
	}
}

Now, when the String thing = list.get(lastIndex); line causes an ArrayIndexOutOfBoundsException, the program automatically jumps to the code inside the catch block. This means that the System.out.println(thing); line is not run. Instead, the code inside the catch block is run instead.

Also note that the code inside the catch block is only run when an exception is thrown. If no exception is thrown, then the code inside the catch block is skipped.

Also note that the catch block is given an instance of a class representing the error that occurred. You should check the Java API for useful function, but one you’ll use all the time is printStackTrace():

catch(ArrayIndexOutOfBoundsException e){
	System.out.println("You forgot to add something to the ArrayList.");
	e.printStackTrace();
}

This allows you to add a friendly error message, but still prints the stack trace to the console so you can figure out exactly what caused the error.

Checked Exceptions

In the above code, note that we could use a try and catch block, but we didn’t have to. That’s because ArrayIndexOutOfBoundsException is an unchecked exception. If a function might throw an unchecked exception, you don’t have to do anything special.

However, functions can also throw checked exceptions, which you do have to catch. Look at this program:

import java.io.PrintWriter;

public class FileMaker{

	public static void main(String[] args){
		PrintWriter output = new PrintWriter("OutputFile.txt");
		output.write("Happy Coding!");
		output.close();
	}
}

This code uses the PrintWriter class to create a file named OutputFile.txt. First, we create an instance of PrintWriter and give it the name of a file to create. Then we write the contents of the file, and finally we call the close() function to free the file up (otherwise we might get errors when we try to open the file in a text editor). As always, you should check out the Java API to learn more about new classes.

But if you try to compile this code, you’ll get an error:

> javac FileMaker.java
FileMaker.java:6: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
                PrintWriter output = new PrintWriter("OutputFile.txt");
                                     ^
1 error

This error tells us that the PrintWriter output = new PrintWriter("OutputFile.txt"); line of code can give us a FileNotFoundException, so we have to use a try and catch block with this code. That’s because FileNotFoundException is a checked exception, so we have to specifically catch possible errors.

So, we can put our code inside a try block, and we add a catch block that catches the FileNotFoundException. We just call the printStackTrace() function so we can debug the error if it ever happens.

import java.io.PrintWriter;
import java.io.FileNotFoundException;

public class FileMaker{

	public static void main(String[] args){
		try{
			PrintWriter output = new PrintWriter("OutputFile.txt");
			output.write("Happy Coding!");
			output.close();
		}
		catch(FileNotFoundException e){
			e.printStackTrace();
		}
	}
}

Now we’re specifically catching the FileNotFoundException, so the compiler is happy and our code is safe.

Note: Never leave a catch block empty! You’ll have no way to know that anything went wrong, which makes it very hard to debug your code when something does go wrong. And you’ve probably learned by now that things do go wrong all the time when you’re programming! So at least put a call to printStackTrace() inside a catch block, even if you don’t think the exception will ever actually happen.

Logic Errors

Sometimes you’ll write code that compiles, and doesn’t throw any runtime exceptions, but still doesn’t work how you expected it to. This is almost always caused by a logic error, and it’s usually a result of the programmer (that’s you!) making an assumption or a typo.

Let’s look at this program:

public class ArrayCounter{

	public static int countArray(int[] array){
		int total = 0;
		for(int i = 1; i < array.length; i++){
			total += array[i];
		}
		return total;
	}
	
	public static void main(String[] args){
		int[] array = {1, 2, 3};
		int total = countArray(array);
		System.out.println("The total is: " + total);
	}
}

This program creates a countArray() function that takes an int[] array parameter. It loops over the array and adds every number to the total, which it then returns. Then the main() function creates an array, calls the countArray() function with it, and then prints out the resulting total.

If you compile and run this program, you’ll see that it prints out The total is: 5, even though the total should be 6! Can you spot the problem?

The problem is this line of code:

for(int i = 1; i < array.length; i++){

Remember that arrays are 0-based, so the first index is always 0, not 1. So this loop actually starts at the second index, and it skips the first number! To fix the logic error, we need to make the loop start at 0:

for(int i = 0; i < array.length; i++){

But even if we fix that error, things can still be wrong. Try compiling and running this:

public class ArrayCounter{

	public static int countArray(int[] array){
		int total = 0;
		for(int i = 0; i < array.length; i++){
			total += array[i];
		}
		return total;
	}
	
	public static void main(String[] args){
		int oneBillion = 1000000000;
		int[] array = {oneBillion, oneBillion, oneBillion};
		int total = countArray(array);
		System.out.println("The total is: " + total);
	}
}

You might expect this to print out 3 billion, but it actually prints out a negative number! What’s going on?

Remember that the different number types (like int and double) hold different types of numbers. The int type holds whole numbers, but they have to be between -2,147,483,648 and 2,147,483,647. This is because the number is stored in a 32-digit binary number, but you don’t really have to understand that part. The point is that when we try to hold 3 billion in an int value, the number goes over the limit and starts back over at the beginning, which is a negative number. That’s why we get a negative number instead of 3 billion.

To fix this error, we could use a long variable, which can hold larger values. For more info on data types in Java, check out the Java documentation.

The lesson is that even if you write code that compiles and runs without any exceptions, it still might contain logic errors. This doesn’t mean you’re a bad programmer! They happen all the time to every single one of us.

Debugging

When you encounter a runtime exception or a logic error, that means it’s time to debug your program. You need to figure out exactly what’s going on in your code, and exactly what in the code is behaving differently from what you expected.

One of the easiest ways to do that is by adding System.out.println() statements to your code. This can help you figure out the values of variables, which functions are being called in what order, how many times a loop is iterating, etc.

Let’s say we have a goal of writing a function that takes a String direction as a parameter, and returns the direction after rotating 90 degrees. We might write this program:

public class DirectionRotater{
	
	public static String rotate(String direction){
		if(direction.equals("up")){
			direction = "right";
		}
		if(direction.equals("right")){
			direction = "down";
		}
		if(direction.equals("down")){
			direction = "left";
		}
		if(direction.equals("left")){
			direction = "up";
		}
		return direction;
	}
	
	public static void main(String[] args){
		String direction = "up";
		direction = rotate(direction);
		direction = rotate(direction);
		System.out.println("Direction: " + direction);
	}
}

This program creates a rotate() function that takes a String parameter. Depending on the value of that parameter, the function reassigns the variable to be a rotated direction: up turns into right, right turns into down, down turns into left, and left turns into up. If this doesn’t make sense, look at the arrow keys on your keyboard, and imagine rotating each key by 90 degrees. Then the function just returns the value.

The main() function then creates a direction variable with a value of "up". The code passes that into the rotate() function, and then reassigns the direction variable to the value returned from that function. It does this twice, so we’d expect the printed direction to be down. But instead, we see that the direction is up!

> java DirectionRotater
Direction: up

(In fact, no matter what direction we pass into the rotate() function, it always returns "up"!)

To figure out what’s going on, we need to read through the program and make sure each line of code is doing what we expect it to. Try reading through the code in your head first, trying to figure out exactly what each line does. Use a piece of paper to jot down variable values.

If you still can’t figure it out, then you should add print statements to your program. Add as many as it takes to figure out exactly what the code is doing.

public class DirectionRotater{
	
	public static String rotate(String direction){
		System.out.println("rotate function got param: " + direction);
		if(direction.equals("up")){
			System.out.println("Entered first if statement.");
			direction = "right";
			System.out.println("Direction is now: " + direction);
		}
		if(direction.equals("right")){
			System.out.println("Entered second if statement.");
			direction = "down";
			System.out.println("Direction is now: " + direction);
		}
		if(direction.equals("down")){
			System.out.println("Entered third if statement.");
			direction = "left";
			System.out.println("Direction is now: " + direction);
		}
		if(direction.equals("left")){
			System.out.println("Entered fourth if statement.");
			direction = "up";
			System.out.println("Direction is now: " + direction);
		}
		
		System.out.println("Returning: " + direction);
		return direction;
	}
	
	public static void main(String[] args){
		String direction = "up";
		System.out.println("Direction starts as: " + direction);
		direction = rotate(direction);
		System.out.println("Rotated once. Direction is now: " + direction);
		direction = rotate(direction);
		System.out.println("Rotated twice. Direction is now: " + direction);
		System.out.println("Direction: " + direction);
	}
}

We’re using System.out.println() to figure out exactly which functions are being called, which if statements are being entered, and the value of variables. Now when we run the code, we see this:

> java DirectionRotater
Direction starts as: up
rotate function got param: up
Entered first if statement.
Direction is now: right
Entered second if statement.
Direction is now: down
Entered third if statement.
Direction is now: left
Entered fourth if statement.
Direction is now: up
Returning: up
Rotated once. Direction is now: up
rotate function got param: up
Entered first if statement.
Direction is now: right
Entered second if statement.
Direction is now: down
Entered third if statement.
Direction is now: left
Entered fourth if statement.
Direction is now: up
Returning: up
Rotated twice. Direction is now: up
Direction: up

Now if we step through the code while reading these print statements, we can see that the rotate() function is getting the correct parameter, but for some reason it’s entering every single if statement instead of just one of them.

This is because we forgot the else part of our if statements! So the rotate() function reaches the first if statement, which it enters because direction starts out as "up". It reassigns direction to be "right”, which is correct so far. But then it looks at the next if statement, which checks whether direction is "right"! It is, so it reassigns it to be "down". This process repeats for all of the if statements, until the last one reassigns direction to be "up".

To fix this, we need to use else if statements:

if(direction.equals("up")){
	direction = "right";
}
else if(direction.equals("right")){
	direction = "down";
}
else if(direction.equals("down")){
	direction = "left";
}
else if(direction.equals("left")){
	direction = "up";
}

Now when one of the if statements is entered, it doesn’t evaluate any of the other if statements. This causes the direction to be rotated only once, which was our original goal!

Let’s look at another example. Let’s say we have a goal of creating a class that represents a line segment. It should hold two Point objects and contain a getLength() function that returns the distance between those points. We might write this code:

import java.awt.Point;

public class LineSegment{

	private Point startPoint;
	private Point endPoint;
	
	public LineSegment(Point startPoint, Point endPoint){
		startPoint = startPoint;
		endPoint = endPoint;
	}
	
	public double getLength(){
		double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
		return distance;
	}
	
	public static void main(String[] args){
		Point pointOne = new Point(1, 2);
		Point pointTwo = new Point(100, 200);
		LineSegment line = new LineSegment(pointOne, pointTwo);
		System.out.println("Length: " + line.getLength());
	}
}

This class contains two instances of the Point class (as always, you should check out the Java API whenever you see a class you haven’t seen before) and uses the distance formula to find the length of the line segment.

However, if you run the code you’ll see that the code hits a NullPointerException:

> java LineSegment
Exception in thread "main" java.lang.NullPointerException
        at LineSegment.getLength(LineSegment.java:14)
        at LineSegment.main(LineSegment.java:22)

NullPointerException is probably the most common runtime error, and it means that your code uses a variable that doesn’t have a value. You can debug a NullPointerException exactly like any other error: by stepping through the code and understanding exactly what each line does, and by adding print statements that help you understand exactly what’s happening.

For example, the stack trace tells us that the error is happening on this line:

double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
		return distance;

Which means that one of the variables on that line doesn’t have a value. More specifically, the value is null. To figure out exactly which variable is null, we can print out their values just before the line that hits the exception:

public double getLength(){
	System.out.println("startPoint: " + startPoint);
	System.out.println("endPoint: " + endPoint);
	double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
	return distance;
}

When we run that code, we can see that both startPoint and endPoint are null!

> java LineSegment

startPoint: null
endPoint: null

Exception in thread "main" java.lang.NullPointerException
        at LineSegment.getLength(LineSegment.java:16)
        at LineSegment.main(LineSegment.java:24)

So now we have to work backwards and find out why those values are null. We could add more print statements:

import java.awt.Point;

public class LineSegment{

	private Point startPoint;
	private Point endPoint;
	
	public LineSegment(Point startPoint, Point endPoint){
		System.out.println("in constructor, parameter startPoint: " + startPoint);
		System.out.println("in constructor, parameter endPoint: " + endPoint);
		
		startPoint = startPoint;
		endPoint = endPoint;
		
		System.out.println("in constructor, this.startPoint: " + this.startPoint);
		System.out.println("in constructor, this.endPoint: " + this.endPoint);
	}
	
	public double getLength(){
		System.out.println("startPoint: " + startPoint);
		System.out.println("endPoint: " + endPoint);
		double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
		return distance;
	}
	
	public static void main(String[] args){
		Point pointOne = new Point(1, 2);
		Point pointTwo = new Point(100, 200);
		
		System.out.println("in main, pointOne: " + pointOne);
		System.out.println("in main, pointTwo: " + pointTwo);
		
		LineSegment line = new LineSegment(pointOne, pointTwo);
		System.out.println("Length: " + line.getLength());
	}
}

Then we can run that code to get the output:

> java LineSegment
in main, pointOne: java.awt.Point[x=1,y=2]
in main, pointTwo: java.awt.Point[x=100,y=200]
in constructor, parameter startPoint: java.awt.Point[x=1,y=2]
in constructor, parameter endPoint: java.awt.Point[x=100,y=200]
in constructor, this.startPoint: null
in constructor, this.endPoint: null
startPoint: null
endPoint: null

Exception in thread "main" java.lang.NullPointerException
        at LineSegment.getLength(LineSegment.java:22)
        at LineSegment.main(LineSegment.java:34)

Now you can use the output to help you trace through the code in your head. You can see that the points have values inside the main() function, and the parameters are passed correctly. But then the class-level variables aren’t set, which is why they’re null when we call the getLength() function. So something is wrong with how we’re setting the class-level variables using the parameter variables:

startPoint = startPoint;
endPoint = endPoint;

Now that you have the problem narrowed down to just a couple lines, you can go back and read the tutorials for what those lines are doing. In this case, we’re creating a class. You might read through that and realize that we needed to use the this keyword to reference the class-level variables! Without that, these lines are just assigning the parameters back to themselves, which doesn’t change anything. We needed to do this:

this.startPoint = startPoint;
this.endPoint = endPoint;

Now our code sets the class-level variables to the values of the parameters. Now our code works fine!

> java LineSegment
Length: 221.37072977247917

If you still can’t figure it out after debugging, adding print statements, and narrowing it down to just a few lines that aren’t behaving correctly, then you can get help by posting in the forum!

General Tips

Debugging can be frustrating, but it’s a huge part of being a programmer. Here are a couple tips to keep yourself sane:

If all else fails, sometimes the best thing you can do is take a break. Go on a walk, pet your cat, and try to clear your head. You’ll be amazed at how many solutions you end up thinking of in the shower.

Homework

Go back up