Debugging Java Programs in Your IDE and with jdb
Introduction
Every programmer writes bugs. The difference between a frustrating afternoon and a five-minute fix is usually how you look for them. A debugger lets you pause a running program at any line, look at the value of every variable, and step through the code one statement at a time - watching exactly where reality differs from what you expected. This tutorial explains debugging Java programs, in your IDE and with the JDK's command-line debugger, jdb. We will start with a buggy program and the core ideas of debugging - breakpoints, stepping and inspecting. We will then go through the debugger controls and shortcuts in IntelliJ IDEA, Eclipse and VS Code. Next we will find the bug in a real
(Note - Setting up an IDE is covered in Choosing a Java IDEClick to read tutorial: Choosing a Java IDE: IntelliJ IDEA vs Eclipse vs VS Code.) A program with a bug This program should print the average of three marks - 70, 85 and 90 - which is 81.67. It compiles, runs, and prints the wrong answer.
OUTPUT of the above code
The method returns a
Debugging in the IDE
In every major IDE, you set a breakpoint by clicking in the left margin (the gutter) next to a line number - a red dot appears - and then start the program in Debug mode instead of Run mode, with the bug icon next to the run button. When the breakpoint is hit, the IDE highlights the line and shows the Variables and Call Stack panels. The stepping shortcuts are -
These are the default Windows and Linux keymaps; on macOS some shortcuts use
OUTPUT - a jdb session (the commands typed are after each prompt)
Explanation of the session
More powerful debugging features
jdb session. Lastly, we will look at conditional breakpoints, debugging exceptions, remote debugging, and a step-by-step approach to finding bugs.(Note - Setting up an IDE is covered in Choosing a Java IDEClick to read tutorial: Choosing a Java IDE: IntelliJ IDEA vs Eclipse vs VS Code.) A program with a bug This program should print the average of three marks - 70, 85 and 90 - which is 81.67. It compiles, runs, and prints the wrong answer.
A program with a bug
//AverageBug.java
public class AverageBug {
static double average(int[] values) {
int sum = 0;
for (int v : values) {
sum += v;
}
double avg = sum / values.length;
return avg;
}
public static void main(String[] args) {
int[] marks = {70, 85, 90};
System.out.println("Average: " + average(marks));
}
}
Average: 81.0
double, and avg is a double, so where did the fraction go? Rather than guessing, let us pause the program and look.
Core debugging concepts
- Breakpoint: a marker on a line. When execution reaches it, the program pauses before running that line.
- Inspecting variables: while paused, you can see the parameters, local variables and fields of the current method, and evaluate any expression.
- Step over: run the current line, including any method it calls, and pause on the next line.
- Step into: if the current line calls a method, go inside it and pause on its first line.
- Step out: finish the current method, and pause in the caller right after the call.
- Resume: carry on running until the next breakpoint, or the end of the program.
- Call stack: the chain of method calls which led to the current line - the same frames described in Stack vs Heap MemoryClick to read tutorial: Stack vs Heap Memory in Java and Object References. Clicking a frame shows that method's variables.
javac, add the -g option.
| Action | IntelliJ IDEA | Eclipse | VS Code |
| Start debugging | Shift+F9 | F11 | F5 |
| Toggle breakpoint | Ctrl+F8 | Ctrl+Shift+B | F9 |
| Step over | F8 | F6 | F10 |
| Step into | F7 | F5 | F11 |
| Step out | Shift+F8 | F7 | Shift+F11 |
| Resume | F9 | F8 | F5 |
| Evaluate expression | Alt+F8 | Ctrl+Shift+I (Inspect) | Debug Console |
These are the default Windows and Linux keymaps; on macOS some shortcuts use
Cmd or function keys with Fn. For our bug, put a breakpoint on the line double avg = sum / values.length;, debug the program, and look at sum; then step over and look at avg. The command-line session below shows exactly what you would see.
Finding the bug with jdb
jdb, the debugger in the JDK's bin folder, offers the same features from the command line - useful on servers without an IDE, and a good way to see what an IDE's debugger does under the hood. Start it with the class to debug (compiled with javac -g), then type commands at its prompt -stop at Class:line- set a breakpoint;stop in Class.method- break when a method is entered.run- start the program;cont- resume after a breakpoint.locals- show the current method's parameters and local variables;print expression- evaluate an expression.next- step over;step- step into;step up- step out;where- show the call stack.
javac -g AverageBug.java jdb AverageBug
Initializing jdb ... > stop at AverageBug:8 Deferring breakpoint AverageBug:8. It will be set after the class is loaded. > run run AverageBug Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable > VM Started: Set deferred breakpoint AverageBug:8 Breakpoint hit: "thread=main", AverageBug.average(), line=8 bci=33 main[1] locals Method arguments: values = instance of int[3] (id=466) Local variables: sum = 245 main[1] print sum / values.length sum / values.length = 81 main[1] next > Step completed: "thread=main", AverageBug.average(), line=9 bci=39 main[1] print avg avg = 81.0 main[1] print sum * 1.0 / values.length sum * 1.0 / values.length = 81.666664 main[1] cont > Input stream closed. Average: 81.0
stop at AverageBug:8set a breakpoint on line 8, the division (line 1 is the//AverageBug.javacomment). The class was not loaded yet, so jdb deferred it untilrunloaded the class.- At the breakpoint,
localsshowedsum = 245- correct. So the loop is fine, and the bug must be on line 8 itself. print sum / values.lengthgave81: anintdivided by anintis anint, so the fraction is dropped before the result is stored in thedouble. Afternext,avgwas81.0.- Evaluating
sum * 1.0 / values.lengthconfirmed the fix - making one operand adouble. (jdb's simple expression evaluator shows the result with less precision,81.666664; the program itself would compute 81.66666666666667.) The bug is integer division, explained in Type Conversion and CastingClick to read tutorial: Type Conversion and Casting in Java with Examples.
double avg = (double) sum / values.length;, which gives 81.66666666666667.
- Conditional breakpoints: right-click a breakpoint and add a condition, such as
i == 999orname.equals("Ethan"). The program pauses only when the condition is true - essential for bugs that happen on the 1000th loop iteration. - Exception breakpoints: pause the moment a given exception is thrown - for example every
NullPointerException- showing the exact state that caused it, before the stack unwinds. In jdb, usecatch java.lang.NullPointerException. - Watches: expressions the IDE re-evaluates at every pause, such as
orders.size(). - Changing values: most debuggers let you set a variable to a new value while paused, to test a theory without restarting.
- Logpoints: breakpoints which print a message instead of pausing - like adding a
println()without changing the code. - Remote debugging: start a program with
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005, and the IDE (orjdb -attach 5005) can connect to it - even on another machine. Never leave this enabled on a production server open to the network.
- Reproduce the bug reliably, with the smallest input that shows it.
- Read the evidence - the exact error message and the stack traceClick to read tutorial: Checked vs Unchecked Exceptions in Java; the first line of your own code in it is usually the place to start.
- Form a hypothesis about where the actual and expected behaviour diverge.
- Test it with the debugger - break before the suspect code, check the values going in, step over it, and check the values coming out. Narrow the area down, as we did by checking
sumbefore looking at the division. - Fix the cause, not the symptom, then write a test which would have caught the bug - see unit testing with JUnit 5Click to read tutorial: Unit Testing Basics with JUnit 5 in Java.
System.out.println() debugging still has its place for quick checks, but it means editing, recompiling and removing the prints afterwards - while the debugger shows everything, at any point, without changing the code.
Summary
In this tutorial we first met a program which printed a wrong average, and the core ideas of debugging - breakpoints, inspecting variables, stepping over, into and out, and the call stack. We then went through the debugger controls and shortcuts in IntelliJ IDEA, Eclipse and VS Code. Next we found the bug in a real jdb session, by checking sum, evaluating the division, and confirming the fix - integer division. Lastly, we looked at conditional and exception breakpoints, watches, logpoints and remote debugging, and a systematic approach to finding bugs. In the next tutorial, we will build projects with Maven and GradleClick to read tutorial: Introduction to Maven and Gradle for Java Projects.Core Java Tutorials
Writing Javadoc Comments in Java with ExamplesClick to read tutorial: Writing Javadoc Comments in Java with Examples Introduction to Maven and Gradle for Java ProjectsClick to read tutorial: Introduction to Maven and Gradle for Java Projects Java Naming Conventions and Code Style GuideClick to read tutorial: Java Naming Conventions and Code Style Guide Unit Testing Basics with JUnit 5 in JavaClick to read tutorial: Unit Testing Basics with JUnit 5 in Java Stack vs Heap Memory in Java and Object ReferencesClick to read tutorial: Stack vs Heap Memory in Java and Object References
Writing Javadoc Comments in Java with ExamplesClick to read tutorial: Writing Javadoc Comments in Java with Examples Introduction to Maven and Gradle for Java ProjectsClick to read tutorial: Introduction to Maven and Gradle for Java Projects Java Naming Conventions and Code Style GuideClick to read tutorial: Java Naming Conventions and Code Style Guide Unit Testing Basics with JUnit 5 in JavaClick to read tutorial: Unit Testing Basics with JUnit 5 in Java Stack vs Heap Memory in Java and Object ReferencesClick to read tutorial: Stack vs Heap Memory in Java and Object References