IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
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:
- Click on the Run icon in the gutter area and select the Debug option.
- Invoke context actions on the class or main method by using Alt+Enter (Windows/Linux) or ⌥⏎ (macOS) and choose the Debug action.
- Launch from the Run menu.
- Simply press Shift+ F9 (Windows/Linux) or ⌃(⇧)D (macOS).
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:
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.
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
.
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.
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
.
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.
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().
Now, let’s rerun the code and check whether it is working as expected. Start the application and view the result.
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.
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.
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.
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.
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
.
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:
- Debugger Upskill: Basic and Advanced Stepping
In this Debugger Upskill blog, we’ll explore how basic and advanced stepping actions help you use the IntelliJ IDEA debugger more efficiently. - Debugger Upskill: Variables, Evaluate Expression, and Watches
In this Debugger Upskill blog, we’ll show you how the examination of variables, along with the Evaluate Expression and Watches features, help you interpret the program’s behavior under various conditions.