Advanced Debugging in Java
A doctor doesn't examine every inch of a patient for every complaint. For a cough they listen to the chest; for a sprain they look at the ankle; and when they need to see something they can't reach, they use an X-ray. Debugging works the same way. Clicking "step over" through a whole program is fine for a ten-line exercise, but real bugs hide on the 4,000th loop iteration, inside an exception that is caught somewhere, or on a server you can't run on your laptop. In this lesson, you will learn the debugger features that take you straight to the problem: conditional breakpoints, exception breakpoints, evaluate expression, remote debugging and the command-line debugger jdb. The basics of breakpoints and stepping are in "Using the Debugger".
What Is Advanced Debugging?
Advanced debugging in Java means using the debugger's more precise tools to stop only where the bug is and inspect or test ideas without changing code. Every major IDE (IntelliJ IDEA, Eclipse, VS Code) offers the same core features, because they all talk to the JVM through the same protocol, JDWP (Java Debug Wire Protocol).
| Feature | What it does | When to use it |
|---|---|---|
| Conditional breakpoint | Stops only when a condition is true, such as i == 4000 | The bug appears only for certain data |
| Exception breakpoint | Stops the moment an exception is thrown, even if it is caught later | An exception is swallowed or its origin is unclear |
| Logpoint (non-suspending breakpoint) | Prints a message instead of stopping | You want a quick trace without editing code |
| Evaluate expression | Runs any Java expression on the paused program | Testing a fix or a guess on the spot |
| Drop frame | Rewinds to the start of the current method | You stepped past the interesting line |
| Remote debugging | Debugs a JVM running elsewhere | Bugs that only happen on a server or in a container |
Conditional Breakpoints
A normal breakpoint inside a loop stops on every iteration. A conditional breakpoint stops only when its condition is true.
In IntelliJ IDEA, right-click the breakpoint dot (or press Ctrl+Shift+F8) and type a condition; in Eclipse, open Breakpoint Properties; in VS Code, right-click the gutter and choose Add Conditional Breakpoint. The condition is plain Java that can use any variable visible on that line:
i == 4000 // a specific iteration
student.name().equals("Advitha") // a specific record
total < 0 // a state that should never happenException Breakpoints
When an exception is caught and logged far from where it was thrown, or caught and ignored, the stack trace you finally see may not lead to the cause. An exception breakpoint pauses the program at the exact line that throws, with every variable still in place.
- IntelliJ IDEA: Run → View Breakpoints → + → Java Exception Breakpoints, then choose, for example,
NullPointerException. - Eclipse: the J! button in the Breakpoints view.
- VS Code: tick "Caught Exceptions" or "Uncaught Exceptions" in the Breakpoints panel.
Evaluate Expression
While the program is paused, Evaluate Expression (Alt+F8 in IntelliJ IDEA, Ctrl+Shift+I in Eclipse, the Debug Console in VS Code) runs any expression against the live program: call methods, inspect collections, or try a possible fix before editing the code. It is the fastest way to test a theory.
The Terminal Debugger jdb
The JDK includes jdb, a debugger that runs in the terminal. It is useful on servers without an IDE, and it shows clearly what every IDE does behind the scenes. Let's debug a program that should print the class average of 81.1 but prints 81.0.
public class Average {
static double average(int[] marks) {
int total = 0;
for (int i = 0; i < marks.length; i++) {
total += marks[i];
}
return total / marks.length;
}
public static void main(String[] args) {
int[] marks = {91, 84, 77, 95, 88, 60, 72, 69, 85, 90};
System.out.println("average = " + average(marks));
}
}average = 81.0Compile with -g so the class file includes local variable names, then start jdb and stop on the return line:
javac -g Average.java
jdb -sourcepath . Average
> stop at Average:7
> run
main[1] locals
main[1] print total
main[1] print marks.length
main[1] eval total / marks.length
main[1] contInitializing jdb ...
> Deferring breakpoint Average:7.
It will be set after the class is loaded.
> run Average
VM Started: Set deferred breakpoint Average:7
Breakpoint hit: "thread=main", Average.average(), line=7 bci=22
7 return total / marks.length;
main[1] Method arguments:
marks = instance of int[10] (id=432)
Local variables:
total = 811
main[1] total = 811
main[1] marks.length = 10
main[1] total / marks.length = 81
main[1] > average = 81.0
The application exitedExplanation:
stop at Average:7sets a breakpoint on line 7. It is "deferred" until the class loads.localslists the method's arguments and local variables:totalis 811, which is correct.eval total / marks.lengthshows 81, not 81.1. Both operands areint, so Java does integer division and only then widens the result todouble. The fix is(double) total / marks.length.contlets the program finish.
| jdb command | Description |
|---|---|
stop at Class:line | It is used to set a breakpoint on a line. |
stop in Class.method | It sets a breakpoint at the start of a method. |
catch java.lang.NullPointerException | It stops when that exception is thrown (an exception breakpoint). |
run, cont | They start the program, or continue after a stop. |
step, next, step up | They step into, step over, or run until the current method returns. |
locals, print expr, dump obj | They show local variables, an expression's value, or all fields of an object. |
where | It shows the call stack. |
threads, thread id | They list threads, or switch to one. |
Remote Debugging
Some bugs only happen on a test server or inside a container. Remote debugging starts that JVM with the JDWP agent, which listens on a port, and you attach your debugger to it, exactly as if the program were running locally.
Step 1: start the program with the agent.
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:5005 AverageListening for transport dt_socket at address: 5005Step 2: attach from another terminal (or from the IDE).
jdb -sourcepath . -attach localhost:5005
main[1] stop at Average:7
main[1] cont
main[1] print total
main[1] contInitializing jdb ...
VM Started: > No frames on the current call stack
main[1] Deferring breakpoint Average:7.
It will be set after the class is loaded.
main[1] > Set deferred breakpoint Average:7
Breakpoint hit: "thread=main", Average.average(), line=7 bci=22
7 return total / marks.length;
main[1] total = 811
main[1] >
The application exitedExplanation:
server=ymakes the program listen for a debugger;suspend=ymakes it wait for one before runningmain, so you don't miss the start. Usesuspend=nfor a server that should start normally.address=localhost:5005accepts connections only from the same machine. In an IDE, create a "Remote JVM Debug" configuration with host and port 5005 instead of usingjdb.- After attaching, breakpoints and inspection work exactly as before.
In Practice
| Symptom | Tool to reach for |
|---|---|
| Wrong result only for one record in a big list | Conditional breakpoint on that record |
| "Something threw an exception somewhere" | Exception breakpoint on that type |
| Need a trace without stopping | Logpoint |
| Bug only on the test server | Remote debugging over an SSH tunnel |
| Program hangs | A thread dump (jcmd <pid> Thread.print), then debug the stuck thread |
Common Mistakes
Debugging without -g. Without debug information, jdb can't show local variable names. IDEs and build tools add it automatically.
Slow conditional breakpoints in hot loops. The condition is evaluated every time the line runs; in a loop of millions it can slow the program a lot. Narrow the location first.
Evaluate expression with side effects. Calling a method that changes state (such as list.remove(0)) really changes the paused program.
Leaving suspend=y on a server. The server won't start until a debugger attaches.
Practice
- Easy: Fix
Average.javawith a cast, run it again, and confirm it prints 81.1. Then usejdbwithstop in Average.averageandstepto walk through the loop twice. - Harder: Change the marks array to include a negative number by mistake. In your IDE, add a conditional breakpoint
marks[i] < 0inside the loop, and find which index holds the bad value without stepping through the others.
Key Takeaways
Was this tutorial helpful?