Skip to content

VOL 00 / CH 06 / LESSON 01

6.1 Understanding Errors and Call Stacks ​

Prerequisites: Chapter 2 (Terminal Basics) and Chapter 5 (Package Management) Estimated time: 15 minutes Reading approach: First determine the failure phase, then extract evidence using the language's stack format You are ready to continue when: you can distinguish between compile-time errors and runtime exceptions, and identify the next investigative clue from a Java, Python, or Node.js stack trace

After a program fails, Git can tell you what changed and a debugger can pause a running process. Before changing code, read the evidence in the error output and call stack.

The Program Crashed ​

You add one line of code, compilation succeeds, and the next launch fills the screen with errors. Exception, Thread, NullPointer, and at are familiar words, but together they look like the log has volunteered for overtime. Do not guess yet: identify the phase that failed, then trace how execution reached that point.

First, Clarify Five Key Points ​

ClueMeaningDoes Not Mean
Compilation or parsing errorThe source code failed to pass through a compiler or parserDoes not prove that the application ran
ExceptionAn abnormal condition occurred during executionA caught exception does not necessarily terminate the process
Stack traceA snapshot of the call path at the time of an exceptionDoes not automatically indicate the root cause
stdout / stderrTwo output channelsThe presence of content in stderr does not necessarily mean failure
Exit codeThe status code indicating how a process terminatedThe specific non-zero value is defined by the program itself

Here, main.py stands for the program being diagnosed. Save the Python example later on this page before trying these commands. Check the exit code immediately after the command:

bash
python3 main.py
echo "$?"

To preserve evidence, record the status and save both output streams. This handles the expected failure even if the calling shell enables set -e:

bash
status=0
python3 main.py >stdout.log 2>stderr.log || status=$?
printf 'status=%s\n' "$status"

Terminal colors are not reliable indicators. Always verify the raw output, the exit code, and the program's documentation.

Java: Tracing Exceptions from the Stack Frame Toward the Caller ​

text
Exception in thread "main" java.lang.NullPointerException: <Diagnostic message varies by JDK>
    at Main.processData(Main.java:8)
    at Main.main(Main.java:4)

This message is presented in sequence as follows:

  1. The thread that encountered the error is main;
  2. The exception type is NullPointerException;
  3. The message after the colon is a diagnostic provided by the JDK, whose exact wording may vary by version;
  4. processData represents the stack frame closest to where the exception was thrown;
  5. main is the caller of that frame.

Save this as Main.java, compile it with the JDK's javac, and run it with java. The earlier trace illustrates the structure; use the paths and messages from your own output:

java
public class Main {
    public static void main(String[] args) {
        String input = null;
        processData(input);
    }

    static void processData(String data) {
        System.out.println(data.length());
    }
}
bash
javac Main.java
java Main
echo "$?"

Open the file and line number within the stack frame to inspect which values are used on that line, then trace back where those values originated. Adding a null check to data might prevent the crash, but it doesn’t necessarily explain why the value is null.

Caused by and Suppressed ​

text
java.lang.IllegalStateException: cannot load workshop configuration
    at com.example.Boot.start(Boot.java:20)
Caused by: java.nio.file.NoSuchFileException: application.properties
    at java.base/sun.nio.fs.UnixException.translateToIOException(...)

The outer exception describes the current layer of abstraction, while Caused by: identifies an underlying exception. Continue through the exception chain, but also revisit the outer exception for business context.

Suppressed: typically indicates a secondary exception occurring during the handling of the primary exception, such as a failure to close a resource. It is not synonymous with Caused by:.

Python: Types and Messages Usually Appear at the End ​

python
def process_data(data):
    return len(data)

process_data(None)
text
Traceback (most recent call last):
  File "main.py", line 4, in <module>
    process_data(None)
  File "main.py", line 2, in process_data
    return len(data)
TypeError: object of type 'NoneType' has no len()

Reading the traceback:

  1. Start with the final line containing TypeError and the error message.
  2. Move upward to find the most recent frame related to your code.
  3. Trace back through earlier frames to verify how the parameters were passed in.
  4. If the message says "During handling…" or "direct cause…", read both the preceding and following error messages together.

Syntax errors occur before execution and often include a caret symbol indicating where the parser encountered a problem. That position may simply be where parsing stopped, sometimes the missing parenthesis or quote is actually on the previous line.

Node.js: Messages Come First, Stack Frames Follow ​

javascript
function processData(data) {
  return data.length;
}

processData(null);
text
TypeError: Cannot read properties of null (reading 'length')
    at processData (/app/main.js:2:15)
    at Object.<anonymous> (/app/main.js:5:1)

Save the JavaScript as main.js and run node main.js to reproduce it. The output above shows the relevant frames; paths depend on the working directory. The numbers after the filename are usually line:column. If you're running a transpiled or bundled output, verify that source maps and the deployed version match; otherwise, the line numbers may not accurately correspond to the original source code.

Don't Mechanically Hunt for "Your First Line of Code" ​

When dealing with many third-party library frames, prioritize frames that are directly related to your inputs, configurations, and calls, don’t remove all library frames at once:

  • Library-generated error messages may precisely identify invalid parameters;
  • When a framework wraps an exception, the root cause might lie deeper in the Caused by:;
  • Asynchronous queues, thread pools, and remote services can break the local call stack;
  • If source code from the same module has inconsistent versions, line numbers can mislead.

A more reliable four-question approach is:

  1. What is the most specific exception type and message?
  2. Which stack frame is closest to the failed operation?
  3. Which upstream frame provided invalid input or an erroneous state?
  4. Is there an exception chain, request ID, or nearby logs that provide context?

Quick Reference ​

What you seeNext step
The compiler reports dozens of errors in a rowFix the earliest one first, then recompile
Java Caused by:Expand the inner cause, then revisit the outer semantics
Python tracebackRead the last line first, then locate the most recent relevant frame
Node file:line:columnVerify the actual runtime file against the source map
Only "process exited"Save stdout, stderr, and the exit code
Line numbers don't match sourceCross-check the build output, version, and debug symbols

Before You Leave This Page ​

  • Run the Java or Python examples and identify the exception type, error message, failing frame, and calling context.
  • Explain why compilation success does not guarantee runtime success.
  • Clarify that there is no one-to-one correspondence between stderr, exceptions, and non-zero exit codes.
  • When encountering an exception chain, do not discard Caused by: or Python’s chained exceptions.

You now recognize stages, exceptions, and call paths from red-highlighted output. But when no one is watching the terminal at night, the runtime state must still be preserved.

→ Next: Logging Fundamentals

Built with VitePress | Software Systems Atlas