Tips & Tricks Tutorials

Debugger Basics for Java Code in IntelliJ IDEA

If you’ve ever used System.out.println() to debug your code, this post is a must-read.

A debugger is a special tool that you can use to execute your Java code in a strictly controlled environment. It lets you review and analyze the inner state of your application and find and fix bugs that may be hidden deep within your code.

With a debugger, you also have the power to change and test the behavior of your code without modifying the source, and do a lot of other interesting things too. Let’s take our first steps into the world of debugging and see what this tool is capable of.

Executing code in debug mode

Starting a debugger in IntelliJ IDEA for a console application is simple.

Start debugging in IntelliJ IDEA

Let’s use the following sample code to demonstrate:

package com.jetbrains;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Coordinates {
   public static void main(String[] args) throws IOException {
       List<Point> lineCoordinates = createCoordinateList();
       outputValues(lineCoordinates);
       Point p = new Point(13, 30);
       removeValue(lineCoordinates, p);
       outputValues(lineCoordinates);
   }

   private static void outputValues(List<Point> lineCoordinates) {
       System.out.println("Output values...");
       for (Point p : lineCoordinates) {
           System.out.println(p);
       }
   }

   private static void removeValue(List lineCoordinates, Point p) {
       lineCoordinates.remove(p);
   }

   private static List createCoordinateList() {
       ArrayList list = new ArrayList<>();
       list.add(new Point(12, 20));
       list.add(new Point(13, 30));
       return list;
   }
}

There are a number of ways to start the debugger:

  1. Click on the Run icon in the gutter area and select the Debug option.
  2. Invoke context actions on the class or main method by using Alt+Enter (Windows/Linux) or ⌥⏎ (macOS) and choose the Debug action.
  3. Launch from the Run menu.
  4. Simply press Shift+ F9 (Windows/Linux) or ⌃(⇧)D (macOS).

Launch Debugger in IntelliJ IDEA

Overview of the sample application

The code used in this blog is simple. The method createCoOrdinateList() creates two instances of the Point class and adds them to an ArrayList. The Point class has two fields, x and y, and getter and setter methods. The outputValues() method outputs the passed list items to the console. The next line of code creates a Point instance and the removeValue() method tries to remove it from the lineCoordinates list.

When you execute this code, you’ll see in the output that even though a Point with x and y values 13 and 30 were added to the list, when another instance with identical values was created to remove it, it was not successful. Let’s debug the code.

To debug your code, you’ll need to know the various step actions you can use to move through your code to find the bugs.

Debug Window

The debug window displays important information when your application suspends execution on a breakpoint, like frames, threads, console window, step action icons, variables pane, and much more:

Debugger interface in IntelliJ IDEA

If you close the Debug Window by mistake, you can always reopen it using the shortcut Alt+5 (Windows/Linux) or ⌘5 (macOS).

Pause, resume, restart, or stop the debugger

If your application seems to be unresponsive, you can pause the program to analyze where your code is stuck. Let’s modify the main method from the preceding section as follows:

    public static void main(String[] args) throws IOException {
        List<Point> lineCoordinates = createCoordinateList();
        outputValues(lineCoordinates);
        Point p = new Point(13, 30);
        int y = System.in.read();         // execution pauses here 
        removeValue(lineCoordinates, p);
        outputValues(lineCoordinates);
    }

Execute your application in debug mode. If you don’t input a value, your application would seem to become unresponsive.

In the Debug window, click on Pause Program and the editor window will show the class and method your application is currently executing – or blocked on. In this example, you can see that the code is blocked for user input, showing the relevant class and method in the editor. You can also view the call stack. By clicking on the method calls in the call stack, you can view the corresponding class and method in the editor window.

You can resume program execution by clicking on Resume Program or by using the shortcut F9. To restart the program in debug mode, select Rerun. You can stop debugging your program at any time by using the Stop icon.

Stop, pause, resume Java debugging

Notice that I didn’t set any breakpoints in this case.

Breakpoints

A breakpoint will stop the execution of your program, so that you can analyze the state of your code.

To set a breakpoint on a line of code, click in the gutter area or use the shortcut Ctrl+F8 ( Windows/Linux) or ⌘F8 (macOS). If you don’t want to stop execution every time it reaches a breakpoint, you can define a condition for the breakpoint. For example, let’s add a breakpoint in the method outputValues(), on the line of code that outputs the value of variable p and define a condition to stop code execution when the field y of reference variable p is equal to 30.

Set a condition for a breakpoint

You can also drag-drop the breakpoint in the gutter and move it to another line of code. By default, clicking a breakpoint icon in the gutter will delete it (you can modify the default behavior in Settings/Preferences). But if you’ve defined conditions or other parameters for a breakpoint, you might prefer it to be disabled, rather than deleted, when you click on it. You can do this by right-clicking the breakpoint icon and uncheck Enabled. A tick indicates that there is information for this line of code.

Disable a breakpoint

To check how the breakpoint and its conditions work, execute the sample code included in this blog in debug mode. You’ll see that this program will pause when the value of field y for variable p is 30.

Program paused at the set value

There’s much more to breakpoints. You can right-click on the breakpoint icon in the gutter and click on More. In the dialog that opens, you can modify a breakpoint so that it doesn’t suspend the program execution and instead logs an expression when it is reached. Let’s log the value of the x and y fields of the Point class and rerun our code. Now the code execution doesn’t stop at the breakpoint – instead it logs the expression we defined to the console.

Step Actions

There are different ways to navigate your code in debug mode. For example, you might prefer to execute a line of code without bothering about the details of the methods being called. Or you might prefer to see which lines of code execute when you call another method from your application, libraries, or APIs. You can do this through the various step actions.

Set a breakpoint before you start the application in the debug mode. The various step actions are:

  • Step Over (F8) lets you execute a line of code and move on to the next line. As you step through the code, you’ll see the value of the variables next to the code in the editor window. These values are also visible in the Variables pane in the Debug window.
  • Step Into (F7) will take you to the first line of code in a method defined in the same class, or another class in the application.
  • Force Step Into lets you debug methods defined in the APIs or libraries. If the source code of the API or library is not available, IntelliJ IDEA decompiles and debugs it for you.
  • With Step Out, you can skip stepping through code in a method line by line and return to the calling method. The called method executes, but without stepping through each line of code in it.

Step actions in Debugger

Let’s use all the preceding actions to debug the Coordinates class. We’ll start by stepping over the lines of code in the main() method, stepping into the removeValues() method, and force-stepping into the remove() method of the ArrayList class and the equals() method to check how the values of the lineCoordinates list are being compared with the value of reference variable p, so that a matching value can be removed from the list.

In the sample application, we discovered that the ‘bug’ is caused by the way the equals() method compares values. It returns true only if the references match, not if their corresponding field values match.

Let’s fix this bug by overriding the equals() method in the Point class. To do so, call the Generate menu (Alt+Insert on Windows/Linux or ⌘N on macOS) and select equals() and hashCode().

Generate the overriding of the equals() method

Now, let’s rerun the code and check whether it is working as expected. Start the application and view the result.

Run Java code after fixing the bug

Everything looks good now. We managed to find a bug and fix it too!

Variables Pane

The inline debugger is very helpful since it shows the value of the variables in the editor as you step through the code. However, the Variables pane shows a lot more details and includes all fields of variables, including private fields. Clicking on stacks will show us the variables that are relevant to that stack.

Variables Pane in the Debugger

You can right-click on a variable and select Jump To Source (F4 on Windows/Linux or ⌘↓ on macOS) to view where it was declared to understand your code better. By selecting the option Jump To Type Source (Shift+F4 on Windows/Linux or on macOS), you can also view the definition of non-primitive variables.

Jump to Source and Jump to Type Source through Debugger

In a call stack, you might want to evaluate an expression to verify your assumptions. For example, I can evaluate the value of the this variable, or other valid expressions, like this is double equal to an instance of the Point class, or this is .equals() to an instance of the Point class.

Evaluate expressions in the Debugger

You can create a variable whose value is accessible in all the call stacks by adding a new watch. Say, System.getProperty, and use the name of your OS. You can create watches to view the value of certain variables in all the call stacks. Right-click inside the Variables Pane and select New Watch.

Add a Watch in a Debugger

Modify code behavior

Did you know you can change the behavior of your code without changing its source? And this applies to the code defined by another API or framework too.

In this code execution, the x and y fields of Point instances being compared are equal, and this equals() method is about to return true. We can change the value of a variable by right-clicking it in the variable pane and selecting Set Value (F2). When we do this the behavior of the code changes. With the modified value, the equals() method returns false and this value won’t be removed from the ArrayList.

Set a value in the Debugger

Summary

The debugger is a powerful and versatile tool that executes programs in a controlled environment. With a debugger, you can see the inner state of an application, find bugs, understand code, and do many other things.

See also:

image description