Question 6 · Section 7

What is Throwable?

Throwable is a class, not an interface, because it stores state: stack trace, message, cause, suppressed list. An interface cannot store data.

Language versions: English Russian Ukrainian

Junior Level

Definition

java.lang.Throwable is the base class for all objects that can be thrown (throw) and caught (catch). Without inheriting from Throwable, an object cannot be used as an exception.

Throwable is a class, not an interface, because it stores state: stack trace, message, cause, suppressed list. An interface cannot store data.

Hierarchy

Throwable
  ├── Error - fatal JVM errors
  │     ├── OutOfMemoryError
  │     └── StackOverflowError
  └── Exception - application errors
        ├── RuntimeException (unchecked)
        └── Checked Exceptions

Key methods

Throwable t = new Exception("Something went wrong");

t.getMessage();       // "Something went wrong"
t.printStackTrace();  // Prints call stack to System.err
t.getCause();         // Returns the cause (if any)
t.getStackTrace();    // StackTraceElement[] array

When to use

Throwable is rarely used directly. Usually you work with its subclasses:

  • Exception and subclasses - for application errors
  • Error and subclasses - for fatal JVM errors (but they are not caught)

Middle Level

When NOT to use catch(Throwable)

Never use catch (Throwable t) in business logic - it will catch OutOfMemoryError and leave the JVM in an unstable state. Exception: global handler at the application boundary.

Internal structure

Throwable contains a hidden field backtrace (type Object), which is filled by the native method fillInStackTrace(). This field holds the native call stack in JVM memory.

When getStackTrace() is called, the native structure is converted to StackTraceElement[] array - this is a lazy optimization.

Constructor with stack trace control

public Throwable(String message,
                 Throwable cause,
                 boolean enableSuppression,
                 boolean writableStackTrace)

Parameters:

  • enableSuppression - whether to allow suppressed exceptions // Suppressed exceptions - see file ‘What are suppressed exceptions.md’. // Briefly: if one exception was thrown in try and another in close(), // the second is not lost but added to the suppressed list of the first.
  • writableStackTrace - whether to fill the stack trace

Suppressed Exceptions

A mechanism for preserving “lost” exceptions:

try (FileInputStream fis = new FileInputStream("file.txt")) {
    fis.read(); // IOException #1
} // close() throws IOException #2
// #1 - main, #2 - suppressed

When logging through Logback/Log4j2, suppressed exceptions are printed with the Suppressed: prefix.

Serialization

When transmitted over a network (RMI, microservices), the native backtrace is lost. The JVM converts it to Java objects, which increases data size.


Senior Level

Disabling stack trace for performance

For signal exceptions (validation), stack trace is not needed:

public class FastException extends RuntimeException {
    public FastException(String message) {
        super(message, null, true, false); // writableStackTrace = false
    }
}

Speeds up creation by tens of times - native stack walk is not executed.

Immutable Exceptions

In high-load systems, static instances are created:

public static final ValidationException INVALID_EMAIL =
    new ValidationException("Invalid email", null, true, false);

Advantages: does not create objects on each call, no GC load. Disadvantages: identical stack trace for all cases.

Distributed systems

Transmitting full stack traces is an anti-pattern:

// At the service boundary
try {
    service.process();
} catch (Throwable t) {
    // Pass only DTO, not the entire stack
    return ErrorResponse.of(t.getClass().getSimpleName(), t.getMessage());
}

Saves megabytes of traffic during mass failures.

printStackTrace() - never in production

printStackTrace() writes to System.err, which:

  • Is synchronized - thread blocking
  • Does not reach log files without redirection
  • In Kubernetes, is split into separate lines

Use loggers: log.error("Msg", throwable).

Diagnostics

  • toString() - only class name and message
  • printStackTrace() - entire tree (slow)
  • getStackTrace() - array of elements (for programmatic analysis)
  • In distributed systems, use Trace ID / Span ID to link logs between services

Interview Cheat Sheet

Must know:

  • Throwable - base class for all objects that can be throw and catch
  • Subclasses: Error (fatal JVM) and Exception (application errors)
  • Stores state: stack trace, message, cause, suppressed list
  • Key methods: getMessage(), getCause(), getStackTrace(), printStackTrace()
  • Contains hidden field backtrace, filled by native fillInStackTrace()
  • Constructor with writableStackTrace = false disables stack walk (speeds up 10-50x)
  • Suppressed exceptions - preserve exceptions from close() in try-with-resources
  • printStackTrace() - never in production (writes to System.err, synchronized)

Frequent follow-up questions:

  • Why is Throwable a class, not an interface? - Stores state (stack trace, cause, suppressed), interfaces don’t store data
  • When NOT to use catch(Throwable)? - In business logic: will catch OutOfMemoryError; acceptable only in a global handler
  • What are suppressed exceptions? - If try and close() both threw two exceptions, the second is added to the suppressed list of the first
  • Why is printStackTrace() bad for production? - Synchronized, doesn’t reach log files, in Kubernetes is split into lines

Red flags (NOT to say):

  • “I use Throwable instead of Exception for all errors” - it will catch Error too
  • “printStackTrace() is a normal way to log” - use loggers
  • “Throwable is not serializable” - it is, but native backtrace is lost

Related topics:

  • [[What is at the top of the exception hierarchy]]
  • [[What is the difference between Error and Exception]]
  • [[What are suppressed exceptions]]
  • [[How to properly log exceptions]]
  • [[What is exception chaining]]