Debugging Java Programs — Techniques, Tools & Best Practices

4 minute read

Every programmer encounters bugs. The difference between a junior and senior developer isn’t the number of bugs they write — it’s how quickly they find and fix them.


Types of Errors

Type When It Happens Example
Compile-time During compilation Missing semicolon, wrong type
Runtime During execution NullPointerException, division by zero
Logical Program runs but gives wrong results Off-by-one error, wrong formula

Compile-time errors are the easiest — the compiler tells you exactly what’s wrong. Logical errors are the hardest — the program doesn’t crash, it just gives wrong answers.


Reading Error Messages

Java error messages are incredibly helpful if you read them carefully.

Compile Error

Main.java:5: error: incompatible types: String cannot be converted to int
    int x = "hello";
            ^

Tells you: file, line number, what’s wrong.

Runtime Exception (Stack Trace)

Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "String.length()" because "name" is null
    at Main.processName(Main.java:15)
    at Main.main(Main.java:8)

Read bottom to top:

  1. main called processName (line 8)
  2. processName crashed at line 15
  3. Reason: tried to call .length() on a null String

Always read the first line of the stack trace — it tells you what went wrong. Then trace the call chain.


Debugging with Print Statements

The simplest technique — add System.out.println() to see variable values:

public static int binarySearch(int[] arr, int target) {
    int low = 0, high = arr.length - 1;

    while (low <= high) {
        int mid = (low + high) / 2;
        System.out.println("low=" + low + " high=" + high + " mid=" + mid + " arr[mid]=" + arr[mid]);

        if (arr[mid] == target) return mid;
        if (arr[mid] < target) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}

Pros and Cons

✅ Pros ❌ Cons
Quick and easy Clutters code
Works anywhere Must remove after debugging
No tools needed Can’t inspect complex objects easily

Tip: Use a consistent prefix like [DEBUG] so you can find and remove them easily.


Using an IDE Debugger

IDE debuggers (IntelliJ IDEA, VS Code, Eclipse) are far more powerful than print statements.

Key Concepts

Feature What It Does
Breakpoint Pauses execution at a specific line
Step Over (F10) Execute current line, move to next
Step Into (F11) Enter a method call
Step Out (Shift+F11) Finish current method, return to caller
Continue (F5) Run until next breakpoint
Watch Monitor a variable’s value
Evaluate Run expressions while paused

How to Debug in an IDE

  1. Set a breakpoint — click the line number gutter
  2. Run in debug mode — click Debug (not Run)
  3. Inspect variables — hover over them or check the Variables panel
  4. Step through — use Step Over/Into to follow execution
  5. Evaluate expressions — test values in the Debug Console

Conditional Breakpoints

Only pause when a condition is met:

Breakpoint at line 15: i == 50

This is invaluable when debugging loops — you don’t want to step through 50 iterations.


Common Bugs and How to Fix Them

NullPointerException

String name = null;
System.out.println(name.length());  // NPE!

Fix: Check for null before using:

if (name != null) {
    System.out.println(name.length());
}

ArrayIndexOutOfBoundsException

int[] arr = {1, 2, 3};
System.out.println(arr[3]);  // index 3 doesn't exist!

Fix: Check bounds: if (index >= 0 && index < arr.length)

Off-By-One Errors

// Bug: skips the last element
for (int i = 0; i < arr.length - 1; i++) { }

// Fix
for (int i = 0; i < arr.length; i++) { }

Infinite Loops

int i = 0;
while (i < 10) {
    System.out.println(i);
    // forgot i++!
}

Fix: Ensure the loop variable is updated.

Integer Division

double avg = 7 / 2;  // 3.0, not 3.5!

// Fix
double avg = 7.0 / 2;  // 3.5

String Comparison with ==

if (input == "yes") { }  // wrong!
if (input.equals("yes")) { }  // correct

Systematic Debugging Strategy

When facing a bug, follow this process:

  1. Reproduce — Can you make the bug happen consistently?
  2. Isolate — What’s the smallest input that triggers it?
  3. Hypothesize — What do you think is wrong?
  4. Test — Add a breakpoint or print to verify
  5. Fix — Change the code
  6. Verify — Does the fix work? Did it break anything else?

Logging vs Print Statements

For real applications, use a logging framework instead of System.out.println():

import java.util.logging.Logger;

Logger logger = Logger.getLogger("MyApp");

logger.info("Processing order #" + orderId);
logger.warning("Inventory low for item: " + itemId);
logger.severe("Database connection failed!");

Benefits:

  • Log levels (INFO, WARNING, SEVERE)
  • Can write to files
  • Can be enabled/disabled without code changes
  • Timestamps and context included automatically

Quick Exercises

  1. This code has a bug. Find and fix it:
    int[] nums = {1, 2, 3, 4, 5};
    int sum = 0;
    for (int i = 1; i <= nums.length; i++) {
        sum += nums[i];
    }
    
  2. Set a breakpoint and step through a recursive Fibonacci method for n = 5.
  3. Write a method with an intentional logical error and use print statements to find it.

Summary

Technique Best For
Read error messages Compile errors, exceptions
Print statements Quick checks
IDE debugger Complex logic, stepping through code
Conditional breakpoints Bugs in loops
Logging Production applications

Debugging mindset:

  • Read the error message carefully
  • Don’t guess — observe actual values
  • Simplify the problem — use minimal test cases
  • Take breaks — fresh eyes find bugs faster

🎉 Module Complete!

Congratulations! You’ve completed Core Java Basics. You now know:

  • Variables, data types, and type casting
  • Operators and expressions
  • Input/output
  • Control flow (if-else, switch, loops)
  • Methods, scope, and recursion
  • Clean code and debugging

You’re ready for the next module — Object-Oriented Programming: where Java truly shines.

Updated: