Definition
- A stack trace shows every method on the call stack when the exception was thrown: exception class, message, and each frame with class name, method name, file name, and line number.
- Correct logging:
log.error("Settlement failed for {}", tradeId, exception)where the exception is the last argument. SLF4J detects the last argument is a Throwable and prints the full stack trace. - Wrong:
log.error(exception.getMessage())loses the stack trace and cause chain.exception.printStackTrace()writes to stderr (not log files), has no timestamp, and is not thread-safe. - Caused by chain: read bottom-up. The last "Caused by:" is the root cause. Each level adds context from the service layer down to the infrastructure.
A stack trace is the incident report at National Bank of India. It says: "The error happened in TradeService.settle() at line 42, called by TradeController.execute() at line 18, called by DispatcherServlet." Correct logging hands the FULL report to the logging system. Wrong logging hands only the first sentence ("trade failed"), and investigators cannot find WHERE it happened.
Understanding the Concepts
What information does a stack trace carry?
A stack trace contains: the exception class and message at the top, followed by one line per stack frame showing class, method, file, and line number.
package com.bank.demo;
public class StackTraceInfoDemo {
static void network() { throw new RuntimeException("Connection refused: 10.0.0.5:5432"); }
static void database() { try { network(); } catch (RuntimeException e) { throw new RuntimeException("Query failed", e); } }
static void service() { try { database(); } catch (RuntimeException e) { throw new RuntimeException("Settlement failed for TRD-001", e); } }
public static void main(String[] args) {
try {
service();
} catch (RuntimeException e) {
// Prints: class, message, and every frame
e.printStackTrace();
}
}
}
java.lang.RuntimeException: Settlement failed for TRD-001
at com.bank.demo.StackTraceInfoDemo.service(StackTraceInfoDemo.java:5)
at com.bank.demo.StackTraceInfoDemo.main(StackTraceInfoDemo.java:8)
Caused by: java.lang.RuntimeException: Query failed
at com.bank.demo.StackTraceInfoDemo.database(StackTraceInfoDemo.java:4)
... 1 more
Caused by: java.lang.RuntimeException: Connection refused: 10.0.0.5:5432
at com.bank.demo.StackTraceInfoDemo.network(StackTraceInfoDemo.java:3)
... 2 more
How do you log an exception correctly?
With SLF4J (the standard logging facade), pass the exception as the last argument. SLF4J detects it is a Throwable and prints the full stack trace.
package com.bank.demo;
public class CorrectLoggingDemo {
public static void main(String[] args) {
RuntimeException exception = new RuntimeException(
"Settlement failed",
new RuntimeException("Connection refused"));
// CORRECT: exception as last argument
// log.error("Settlement failed for {}", "TRD-001", exception);
// Output: full stack trace + Caused by chain
// WRONG 1: getMessage() loses stack trace
System.out.println("Wrong 1: " + exception.getMessage());
// Output: just the message string. No line numbers. No cause.
// WRONG 2: toString() slightly better but still no trace
System.out.println("Wrong 2: " + exception.toString());
// WRONG 3: printStackTrace() not production-safe
// exception.printStackTrace();
// Writes to System.err. No timestamp. Not in log file.
// CORRECT simulation:
System.out.println("Correct: Full trace with cause chain:");
System.out.println(" " + exception.getClass().getSimpleName() + ": " + exception.getMessage());
System.out.println(" Caused by: " + exception.getCause().getMessage());
}
}
Wrong 1: Settlement failed
Wrong 2: java.lang.RuntimeException: Settlement failed
Correct: Full trace with cause chain:
RuntimeException: Settlement failed
Caused by: Connection refused
How do you read a Caused by chain?
Read from bottom to top. The last "Caused by:" is the root cause that tells you what actually went wrong.
package com.bank.demo;
public class ReadCausedByDemo {
public static Throwable getRootCause(Throwable t) {
Throwable cause = t;
while (cause.getCause() != null) {
cause = cause.getCause();
}
return cause;
}
public static void main(String[] args) {
// Simulated 3-level chain
Exception network = new Exception("Connection refused: 10.0.0.5:5432");
Exception sql = new Exception("Unable to acquire connection", network);
RuntimeException settlement = new RuntimeException(
"Settlement failed for TRD-001", sql);
// Reading the chain
System.out.println("Level 1 (business): " + settlement.getMessage());
System.out.println("Level 2 (technical): " + settlement.getCause().getMessage());
System.out.println("Level 3 (root cause): " + getRootCause(settlement).getMessage());
System.out.println();
System.out.println("Root cause tells you: the database server is down.");
System.out.println("Fix the infrastructure, not the code.");
}
}
Level 1 (business): Settlement failed for TRD-001
Level 2 (technical): Unable to acquire connection
Level 3 (root cause): Connection refused: 10.0.0.5:5432
Root cause tells you: the database server is down.
Fix the infrastructure, not the code.
What are common mistakes in exception logging?
package com.bank.demo;
public class LoggingMistakesDemo {
public static void main(String[] args) {
RuntimeException ex = new RuntimeException("Settlement failed",
new RuntimeException("DB timeout"));
// WRONG 1: getMessage() only
System.out.println("Wrong 1: " + ex.getMessage());
// Loses: stack trace, line numbers, cause chain
// WRONG 2: String concatenation with exception
System.out.println("Wrong 2: Error - " + ex);
// toString() only. No stack trace.
// WRONG 3: Logging cause separately
System.out.println("Wrong 3 main: " + ex.getMessage());
System.out.println("Wrong 3 cause: " + ex.getCause().getMessage());
// Loses stack frames. Manual. Error-prone for deep chains.
// CORRECT: log.error("msg", exception)
// The logging framework does everything automatically.
System.out.println("\nCorrect: log.error(\"Settlement failed for {}\", tradeId, exception)");
System.out.println("SLF4J prints: full trace + all Caused by levels");
}
}
Wrong 1: Settlement failed
Wrong 2: Error - java.lang.RuntimeException: Settlement failed
Wrong 3 main: Settlement failed
Wrong 3 cause: DB timeout
Correct: log.error("Settlement failed for {}", tradeId, exception)
SLF4J prints: full trace + all Caused by levels
What should a senior engineer know?
- SLF4J's last-argument convention. Only the last argument is checked for Throwable.
log.error("{}", ex, "text")does NOT print the trace because ex is not last. - Structured logging. In JSON-based logging (Logstash, ELK), the stack trace is included as a field. Use MDC for request correlation IDs.
- Production debugging. When an exception has no stack trace, it may be a pre-built singleton exception with fillInStackTrace overridden. Check for custom exception classes.
- "... N more" in traces. The JVM omits stack frames shared between the cause and the outer exception to save space. The full frames are in the outer exception.
What are common interview follow-up questions?
- What is the correct SLF4J pattern for logging exceptions? (
log.error("msg {}", contextVar, exception)with exception last.) - Why not use printStackTrace()? (Writes to stderr, no timestamp, not thread-safe, not in log files.)
- How do you find the root cause programmatically? (Loop through getCause() until getCause() returns null.)
- What does "... 2 more" mean in a stack trace? (Shared frames omitted because they match the outer exception.)
Visual Overview
Stack Trace Structure:
┌─────────────────────────────────────────────┐
│ RuntimeException: Settlement failed TRD-001 │ ← Business context
│ at TradeService.settle(line 42) │ ← Your code
│ at TradeController.execute(line 18) │ ← Framework
│ Caused by: SQLException: Connection refused │ ← Technical layer
│ at HikariPool.getConnection(line 128) │
│ Caused by: ConnectException: refused │ ← ROOT CAUSE
│ at Socket.connect(line 591) │
└─────────────────────────────────────────────┘
Read BOTTOM-UP for root cause.
Correct: log.error("msg {}", ctx, exception)
Wrong: log.error(exception.getMessage())
Wrong: exception.printStackTrace()
Key Takeaway
A stack trace carries the complete call chain from where the exception was thrown back to the entry point, with class, method, file, and line number for each frame. The "Caused by:" sections show exception chaining, and the root cause is always the last "Caused by:" at the bottom.
The correct way to log an exception is log.error("Settlement failed for {}", tradeId, exception) with the exception as the last argument. SLF4J detects it is a Throwable and prints the full stack trace including all "Caused by:" levels. Never use log.error(exception.getMessage()) which loses the trace, line numbers, and cause chain. Never use exception.printStackTrace() in production because it writes to stderr, has no timestamp, and is not in your log files.
At National Bank of India, Rajesh always passes the exception object as the last argument to the logger. When the ops team investigates a settlement failure, they read the trace bottom-up: SettlementException (business context) caused by DataAccessException (technical layer) caused by ConnectException (root cause: database server down). Fix the infrastructure, not the code.
Remember one sentence: always log the exception object as the last argument so the full stack trace and cause chain are preserved.
---