Питання 6 · Розділ 7

Що таке Throwable?

Throwable -- клас, а не інтерфейс, тому що зберігає стан: стек-трейс, повідомлення, причину, suppressed-список. Інтерфейс не може зберігати дані.

Мовні версії: English Russian Ukrainian

Junior Level

Визначення

java.lang.Throwable — це базовий клас для всіх об’єктів, які можна викинути (throw) і перехопити (catch). Без наслідування від Throwable об’єкт не можна використовувати як виняток.

Throwable – клас, а не інтерфейс, тому що зберігає стан: стек-трейс, повідомлення, причину, suppressed-список. Інтерфейс не може зберігати дані.

Ієрархія

Throwable
  ├── Error — фатальні помилки JVM
  │     ├── OutOfMemoryError
  │     └── StackOverflowError
  └── Exception — помилки додатку
        ├── RuntimeException (unchecked)
        └── Checked Exceptions

Основні методи

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

t.getMessage();       // "Something went wrong"
t.printStackTrace();  // Друкує стек викликів у System.err
t.getCause();         // Повертає причину (якщо є)
t.getStackTrace();    // Масив StackTraceElement[]

Коли використовувати

Безпосередньо Throwable використовується рідко. Зазвичай працюють з його спадкоємцями:

  • Exception і підкласи — для помилок додатку
  • Error і підкласи — для фатальних помилок JVM (але їх не ловлять)

Middle Level

Коли НЕ використовувати catch(Throwable)

Ніколи не використовуйте catch (Throwable t) у бізнес-логіці – це перехопить OutOfMemoryError і залишить JVM у нестабільному стані. Виняток: глобальний обробник на межі додатку.

Внутрішній устрій

Throwable містить приховане поле backtrace (тип Object), яке заповнюється нативним методом fillInStackTrace(). Це поле зберігає нативний стек викликів у пам’яті JVM.

При виклику getStackTrace() нативна структура перетворюється на масив StackTraceElement[] — це ледача оптимізація.

Конструктор з контролем стек-трейсу

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

Параметри:

  • enableSuppression — чи дозволяти suppressed exceptions // Suppressed exceptions – див. файл ‘Що таке suppressed exceptions.md’. // Коротко: якщо в try впав один виняток, а в close() – інший, // другий не втрачається, а додається у suppressed-список першого.
  • writableStackTrace — чи заповнювати стек-трейс

Suppressed Exceptions

Механізм для збереження “втрачених” винятків:

try (FileInputStream fis = new FileInputStream("file.txt")) {
    fis.read(); // IOException #1
} // close() кидає IOException #2
// #1 — основний, #2 — suppressed

При логуванні через Logback/Log4j2 suppressed винятки виводяться з префіксом Suppressed:.

Серіалізація

При передачі по мережі (RMI, мікросервіси) нативний backtrace втрачається. JVM конвертує його у Java-об’єкти, що збільшує розмір даних.


Senior Level

Вимкнення стек-трейсу для продуктивності

Для винятків-сигналів (валідація) стек-трейс не потрібен:

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

Прискорює створення у десятки разів — нативний обхід стека не виконується.

Immutable Exceptions

У високонавантажених системах створюють статичні екземпляри:

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

Переваги: не створює об’єкти на кожен виклик, немає навантаження на GC. Недоліки: однаковий стек-трейс у всіх випадків.

Розподілені системи

Передача повних стек-трейсів — антипатерн:

// На межі сервісу
try {
    service.process();
} catch (Throwable t) {
    // Передаємо лише DTO, а не весь стек
    return ErrorResponse.of(t.getClass().getSimpleName(), t.getMessage());
}

Економить мегабайти трафіку при масових збоях.

printStackTrace() — ніколи у продакшні

printStackTrace() пише у System.err, який:

  • Синхронізований — блокування потоків
  • Не потрапляє у лог-файли без редиректу
  • У Kubernetes розбивається на окремі рядки

Використовуйте логери: log.error("Msg", throwable).

Діагностика

  • toString() — лише ім’я класу та повідомлення
  • printStackTrace() — все дерево (повільно)
  • getStackTrace() — масив елементів (для програмного аналізу)
  • У розподілених системах використовуйте Trace ID / Span ID для зв’язку логів між сервісами

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Throwable — базовий клас для всіх об’єктів, які можна throw і catch
  • Спадкоємці: Error (фатальні JVM) та Exception (помилки додатку)
  • Зберігає стан: стек-трейс, повідомлення, причину (cause), suppressed-список
  • Ключові методи: getMessage(), getCause(), getStackTrace(), printStackTrace()
  • Містить приховане поле backtrace, заповнюване нативним fillInStackTrace()
  • Конструктор з writableStackTrace = false вимикає обхід стека (прискорює у 10-50 разів)
  • Suppressed exceptions — зберігають винятки з close() при try-with-resources
  • printStackTrace() — ніколи у продакшні (пише у System.err, синхронізований)

Часті уточнюючі запитання:

  • Чому Throwable — клас, а не інтерфейс? — Зберігає стан (стек-трейс, cause, suppressed), інтерфейси не зберігають дані
  • Коли НЕ використовувати catch(Throwable)? — У бізнес-логіці: перехопить OutOfMemoryError; допустимо лише у глобальному обробнику
  • Що таке suppressed exceptions? — Якщо в try і close() впали два винятки, другий додається у suppressed першого
  • Чому printStackTrace() поганий для продакшну? — Синхронізований, не потрапляє у лог-файли, у Kubernetes розбивається на рядки

Червоні прапорці (НЕ говорити):

  • “Я використовую Throwable замість Exception для всіх помилок” — це перехопить і Error теж
  • “printStackTrace() — нормальний спосіб логування” — використовуйте логери
  • “Throwable не серіалізується” — серіалізується, але нативний backtrace втрачається

Пов’язані теми:

  • [[Що знаходиться на вершині ієрархії винятків]]
  • [[В чому різниця між Error та Exception]]
  • [[Що таке suppressed exceptions]]
  • [[Як правильно логувати винятки]]
  • [[Що таке exception chaining]]