5 exercises on reading Java, Python, and Node.js stack traces — identify the root cause line, understand frame ordering, and follow exception chains.
Stack trace reading rules
Frames are listed most-recent first — the top frame is where the error occurred
Read the exception type and message first (first line)
Find your code in the frames — skip library/JDK internals
In Python, "During handling of…" signals exception chaining
ECONNREFUSED = nothing is listening on that host:port
0 / 5 completed
1 / 5
🔴Java Stack Trace
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
at com.example.app.StringUtils.countVowels(StringUtils.java:23)
at com.example.app.TextAnalyser.analyse(TextAnalyser.java:58)
at com.example.app.Main.run(Main.java:14)
at com.example.app.Main.main(Main.java:9)
Read the Java stack trace above. Which line contains the root cause of the exception — the line where execution actually failed?
The innermost frame (bottom of the stack) is the root cause.
Java stack traces list frames most-recent-first: the top of the trace is the call that failed, not where the program started.
Frame order (top = innermost):StringUtils.countVowels → called by TextAnalyser.analyse → called by Main.run → called by Main.main
Error type:NullPointerException — a method (String.length()) was called on a null reference (str is null)
Root cause line:StringUtils.java:23 — the null dereference happened here
Entry point:Main.main (line 9) is where the JVM started the program — it is not the error location
Reading strategy for Java traces: ① Read the exception message (top line) — it tells you the type and a short description ② Find your code in the frames (e.g., com.example.app.*) — skip JDK internals ③ The deepest frame in your code is where to start debugging
Key collocation: "thrown at", "propagates up the call stack", "unhandled exception", "catch block"
2 / 5
🔴Python Chained Exception
Traceback (most recent call last):
File "pipeline.py", line 45, in run_pipeline
result = fetch_data(config['endpoint'])
File "pipeline.py", line 29, in fetch_data
response = requests.get(url, timeout=config['timeout'])
KeyError: 'timeout'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "pipeline.py", line 48, in run_pipeline
raise PipelineError("Data fetch failed") from exc
pipeline.PipelineError: Data fetch failed
The Python trace above shows exception chaining. What was the original error that started the chain?
KeyError: 'timeout' is the original root cause.
Python's "During handling of the above exception, another exception occurred" phrase signals exception chaining — one error was caught and a new one raised in its handler.
First exception:KeyError: 'timeout' on line 29. The code accessed config['timeout'] but that key did not exist in the config dictionary.
Handler (line 48): The except block in run_pipeline caught the KeyError and raised a PipelineError with from exc — preserving the chain.
Second exception:PipelineError: Data fetch failed — a higher-level, user-friendly error wrapping the technical detail.
Why chain exceptions? Raising a domain-specific error (PipelineError) from a low-level one (KeyError) lets calling code catch meaningful errors without knowing implementation details. The chain preserves the original for debugging.
Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1148:16) {
errno: -61,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '127.0.0.1',
port: 5432
}
at /app/src/db/pool.js:34:11
at processTicksAndRejections (internal/process/task_queues.js:95:5)
Read the Node.js error above. What does ECONNREFUSED 127.0.0.1:5432 tell a developer about the state of the system?
ECONNREFUSED = the port is not listening — the service is likely down.
Breaking down the error:
ECONNREFUSED — a POSIX error code: the TCP connection attempt was actively refused by the OS at the target address. The OS sends a RST (reset) packet back.
127.0.0.1 — localhost (the same machine). The app is trying to reach a service on the same box.
:5432 — the default PostgreSQL port. Nothing is listening there.
ECONNREFUSED vs. similar errors:
ECONNREFUSED → service is down or port is wrong (active RST)
ETIMEDOUT → firewall drops the packet (no response, connection times out)
ENOTFOUND → DNS hostname can't be resolved (wrong hostname)
ECONNRESET → connection was established but then the peer closed it abruptly
What to check: Is PostgreSQL running? (pg_isready / systemctl status postgresql). Is pool.js:34 using the right host and port from environment variables?
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
at com.example.app.StringUtils.countVowels(StringUtils.java:23)
at com.example.app.TextAnalyser.analyse(TextAnalyser.java:58)
at com.example.app.Main.run(Main.java:14)
at com.example.app.Main.main(Main.java:9)
A colleague says "the bug is in TextAnalyser.java." Based on the stack trace, is this accurate?
The exception originated in StringUtils.java, but TextAnalyser may be responsible for passing null.
This is a subtle but important distinction:
Where the NPE fired:StringUtils.java:23 — the JVM raised the exception here because str was null.
Where null came from: The stack trace alone does not show this. TextAnalyser.analyse at line 58 called countVowels — it may have passed a null string, making it the logical source of the bug.
Two types of blame:
Syntactic location: where the exception is thrown — StringUtils.java:23
Logical root cause: where the invalid state was introduced — possibly TextAnalyser or its caller
Debugging strategy: Fix StringUtils.countVowels to guard against null (if (str == null) return 0;), OR fix TextAnalyser.analyse to not pass null. The right fix depends on the contract: should countVowels accept null, or is that the caller's responsibility?
Traceback (most recent call last):
File "pipeline.py", line 45, in run_pipeline
result = fetch_data(config['endpoint'])
File "pipeline.py", line 29, in fetch_data
response = requests.get(url, timeout=config['timeout'])
KeyError: 'timeout'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "pipeline.py", line 48, in run_pipeline
raise PipelineError("Data fetch failed") from exc
pipeline.PipelineError: Data fetch failed
In the Python trace, which file and line should a developer inspect first to fix the KeyError?
pipeline.py line 29 is where the KeyError occurs — that is the first place to look.
The first traceback says: File "pipeline.py", line 29, in fetch_data response = requests.get(url, timeout=config['timeout']) KeyError: 'timeout'
This tells us:
config['timeout'] raises a KeyError because the 'timeout' key is missing from the config dictionary
Fix options:
Add 'timeout' to wherever the config is built/loaded
Use config.get('timeout', 30) — a safe accessor with a default value
Validate config keys at startup with a schema check
Line 48 is not the fix point — it is the re-raise in the exception handler. Fixing the handler does not fix the underlying missing key.
Line 45 is the call site — it may show which config object is passed, useful context, but the direct failure is on line 29.
Key Python methods:dict.get(key, default), dict.setdefault(), key in dict, Pydantic / marshmallow for schema validation