Phase 5 · Professional DevelopmentModule 30~36 min read

Debugging, Logging & Profiling

Find and fix problems with the debugger, structured logging, and profiling tools.

What you'll learn

Every developer spends serious time finding problems, not just writing code. This module gives you the professional toolkit: reading stack traces, using a real debugger, logging effectively, and profiling to find slow or memory-hungry code.

By the end you'll be able to:

  • Read compiler errors and stack traces to locate a bug fast
  • Use the IDE debugger — breakpoints, stepping, and watches
  • Add structured logging instead of scattering System.out.println
  • Choose the right log level, and understand SLF4J/Logback
  • Profile CPU and memory to find bottlenecks

Reading stack traces

A stack trace looks scary but it's a gift — it tells you exactly what went wrong and where. Read it top-down: the first line names the exception and message; the at lines are the call stack, most recent first. Find the first line that's your code — that's usually where to look:

Stack trace
Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "String.length()" because "name" is null
    at com.app.User.greet(User.java:42)     <- where it broke (your code)
    at com.app.Service.run(Service.java:18)  <- who called that
    at com.app.Main.main(Main.java:9)        <- the entry point

Tip

The helpful NullPointerException message (Module 28) names the exact expression that was null. Combined with the file and line number in the trace, most bugs are located in seconds.

The IDE debugger

println-debugging works, but a real debugger is far more powerful. Set a breakpoint and the program pauses there, letting you inspect every variable, step through line by line, and watch how state changes — no code changes, no recompiling:

Debugger controls

Breakpoint

Pause execution at a chosen line

Step Over

Run the current line, don't dive into calls

Step Into

Descend into the method being called

Step Out

Finish the current method, return to caller

Watch

Track a variable or expression's value

Resume

Continue until the next breakpoint

Note

Advanced tricks: conditional breakpoints (pause only when i == 500) and exception breakpoints (pause the instant a given exception is thrown, anywhere) turn a needle-in-a-haystack bug into a quick catch.

Logging

In real applications you can't attach a debugger to a running server — you need logs. Logging records what your program is doing so you can diagnose issues after the fact. Java has a built-in java.util.logging, though most projects use SLF4J (below):

Logging.java
import java.util.logging.Logger;

class PaymentService {
    private static final Logger log =
        Logger.getLogger(PaymentService.class.getName());

    void charge(int amount) {
        log.info("Charging " + amount);
        if (amount <= 0) {
            log.warning("Rejected invalid amount: " + amount);
            return;
        }
        log.fine("Charge succeeded");   // detailed, usually hidden
    }
}

Watch out

Never log secrets — passwords, tokens, credit-card numbers, personal data. Logs get shared, stored, and indexed. Leaking sensitive data into logs is a real and common security incident.

Log levels

Levels let you control how much detail you see. In production you show INFO and above; while debugging you turn on DEBUG/TRACE. Same code, different verbosity — no need to add and remove print statements:

LevelUse for
ERRORa failure that needs attention
WARNsomething unexpected, but recoverable
INFOhigh-level events (started, order placed)
DEBUGdetailed flow for diagnosis
TRACEthe finest detail, rarely on
java.util.logging uses SEVERE/WARNING/INFO/FINE; SLF4J uses ERROR/WARN/INFO/DEBUG/TRACE.

SLF4J & Logback

The Java ecosystem standardises on SLF4J — a logging facade (interface) — with Logback or Log4j2 as the actual engine behind it. This lets libraries log without forcing a particular implementation on you. Its parameterized {} syntax is both cleaner and faster than string concatenation:

Slf4j.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class UserService {
    private static final Logger log = LoggerFactory.getLogger(UserService.class);

    void login(String user) {
        // {} placeholders are only built if this level is enabled — efficient
        log.info("User {} logged in", user);
        log.debug("Session details: {}", buildDetails());
    }
}

Profiling

When code is slow or leaking memory, don't guess — profile. A profiler measures where time and memory actually go: which methods are hottest (CPU profiling) and what's filling the heap (memory profiling). Java ships Java Flight Recorder (JFR) and Mission Control for exactly this, with low overhead.

Key idea

The golden rule of performance work: measure first. Developers are notoriously bad at guessing bottlenecks. Profile, fix the real hotspot, then measure again (much more in Module 38).

Recap & quick check

Key takeaways

  • Read stack traces top-down; find the first line that's your code.
  • The debugger (breakpoints, stepping, watches) beats println for understanding running code.
  • Use structured logging instead of System.out — and never log secrets.
  • Log levels (ERROR > WARN > INFO > DEBUG > TRACE) control verbosity without code changes.
  • SLF4J is the standard logging facade; profile with JFR to find real bottlenecks — measure first.

Quick check

1. How do you read a stack trace to find where an error occurred?

2. What does a breakpoint do?

3. Which should you NEVER put in logs?

4. What is the purpose of log levels?

5. Before optimizing performance, you should first…

Excellent — you can now find and fix problems like a pro. Next up: Module 31 — Build Tools & Dependency Management.