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:
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 pointTip
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:
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
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):
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
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:
| Level | Use for |
|---|---|
ERROR | a failure that needs attention |
WARN | something unexpected, but recoverable |
INFO | high-level events (started, order placed) |
DEBUG | detailed flow for diagnosis |
TRACE | the finest detail, rarely on |
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:
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
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.