Що таке suppressed винятки?
Найчастіше — у try-with-resources:
Junior Level
Визначення
Suppressed exceptions (придушені винятки) — це механізм Java 7+, який дозволяє одному винятку нести в собі список інших винятків, що сталися паралельно.
Де зустрічається
Найчастіше — у try-with-resources:
try (Resource r = new Resource()) {
r.doWork(); // IOException #1 — основний
} // close() кидає IOException #2 — suppressed
Як отримати
try {
// код
} catch (Exception e) {
System.out.println("Main: " + e.getMessage());
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("Suppressed: " + t.getMessage());
}
}
Навіщо потрібен
До Java 7, якщо в try і в finally виникали винятки, другий затирав перший (shadowing). Розробники втрачали інформацію про кореневу причину помилки.
Чому саме Java 7: у Java 7 з’явився try-with-resources (JLS 14.20.3), який автоматично закриває ресурси. При автоматичному закритті через close() висока ймовірність, що close() викине виняток одночасно з винятком з основного try. Без suppressed механізму один виняток затирав би інший, роблячи налагодження неможливим. Suppressed exceptions — це інфраструктурне доповнення до TWR, а не самостійна фіча.
Коли suppressed НЕ додаються
-
Ручний try-finally — у звичайному
try { ... } finally { close(); }suppressed НЕ додаються автоматично. Виняток зfinallyзатьмарює виняток зtry. Suppressed працює тільки в try-with-resources або при ручному викликуaddSuppressed(). -
enableSuppression = false— якщо виняток створено зnew Exception(msg, cause, false, true), викликиaddSuppressed()ігноруватимуться. -
close()не кидає винятків — якщо ресурс закривається коректно, suppressed список буде порожнім. Це нормальний сценарій. -
Виняток тільки в одному місці — якщо впав тільки
try(але неclose()) або тількиclose()(але неtry), suppressed не буде — буде один звичайний виняток.
Коли НЕ покладатися на suppressed
-
Ручний try-finally — suppressed НЕ працюють автоматично. Якщо ви пишете
try { ... } finally { resource.close(); }і обидва можуть впасти — інформація буде втрачена. Використовуйте try-with-resources. -
Зовнішні бібліотеки без TWR — якщо стороння бібліотека не реалізує
AutoCloseableабо закриває ресурси вручну, suppressed не додадуться. -
Async-контекст — у
CompletableFutureабо реактивних стримах винятки з різних потоків не агрегуються через suppressed автоматично. -
Batch-обробка без патерну — якщо обробляєте пачку задач і кожна може впасти, suppressed не додадуться самі — потрібно вручну використовувати
addSuppressed().
Коли НЕ використовувати suppressed exceptions
- Довгоживучі об’єкти-синглтони — accumulation suppressed винятків веде до memory leak
- Серіалізація через мережу — увесь список suppressed передається по мережі, збільшуючи payload
- Highload-системи — кожен suppressed виняток = об’єкт + стек-трейс у купі
- Як заміна нормальній обробці помилок — suppressed не замінюють try-catch на кожному ресурсі
- У бізнес-логіці — suppressed призначені для infrastructure коду (ресурси, batch-обробка)
Middle Level
Внутрішня реалізація
У Throwable є поле private Throwable[] suppressedExceptions (масив, а не список — для продуктивності).
- Ініціалізується ліниво — тільки при першому виклику
addSuppressed - Можна вимкнути при створенні:
new Exception(msg, cause, false, true)—enableSuppression = false
Як компілятор генерує код для try-with-resources
Коли ви пишете:
try (Resource r = new Resource()) {
r.doWork();
}
Компілятор розгортає це в (спрощено):
Resource r = new Resource();
Throwable primaryException = null;
try {
r.doWork();
} catch (Throwable e) {
primaryException = e;
throw e;
} finally {
if (r != null) {
if (primaryException != null) {
try {
r.close();
} catch (Throwable closeException) {
primaryException.addSuppressed(closeException);
}
} else {
r.close(); // Немає основного винятку — close() летить як звичайний
}
}
}
Ключовий момент: компілятор зберігає посилання на основний виняток (primaryException) і при помилці close() додає його як suppressed до основного, а не замінює його.
Параметри конструктора Throwable
public Throwable(String message, Throwable cause,
boolean enableSuppression, boolean writableStackTrace)
enableSuppression— дозволяє/забороняє suppressed (true за замовчуванням)writableStackTrace— якщо false, стек-трейс не заповнюється (прискорення створення винятку)
Роль у try-with-resources
Сценарій:
- Читаєте файл у
try→IOException(диск відключився) - TWR закриває файл → другий
IOException(диска немає) - У старій Java другий затер би перший
- У сучасній: перший летить до вас, другий —
e.getSuppressed()
Ручне використання
Хоча TWR робить це автоматично, можна використовувати вручну:
Exception mainException = new BatchException("Batch failed");
for (Task task : tasks) {
try {
task.execute();
} catch (Exception e) {
mainException.addSuppressed(e);
}
}
if (mainException.getSuppressed().length > 0) {
throw mainException;
}
Обмеження
- Self-Suppression:
e.addSuppressed(e)→IllegalArgumentException - Null Suppression:
e.addSuppressed(null)→NullPointerException - Order: придушені зберігаються в порядку додавання. У TWR — зворотний порядок оголошення ресурсів (LIFO)
Senior Level
Memory Leak Risk
Нескінченне додавання suppressed винятків до довгоживучого об’єкта (синглтон) — витік пам’яті. Кожен виняток тягне свій стек-трейс.
Serialization Cost
Увесь список suppressed винятків серіалізується разом з основним. У розподілених системах — величезні об’єми даних по мережі.
Batch Processing патерн
public class BatchException extends RuntimeException {
private final List<Throwable> errors = new ArrayList<>();
public BatchException(String message) {
super(message);
}
public void addError(Throwable t) {
if (errors.isEmpty()) {
// Перший виняток — як suppressed до самого себе
addSuppressed(t);
} else {
addSuppressed(t);
}
errors.add(t);
}
public int getErrorCount() {
return errors.size();
}
}
Custom Resource Management
Якщо пишете свій менеджер ресурсів (пул потоків, транзакційний менеджер), що не використовує стандартний TWR:
public class CustomResourceManager implements AutoCloseable {
private final List<Resource> resources = new ArrayList<>();
private Throwable primaryException;
public void close() {
for (Resource r : resources) {
try {
r.close();
} catch (Throwable t) {
if (primaryException == null) {
primaryException = t;
} else {
primaryException.addSuppressed(t);
}
}
}
if (primaryException != null) {
throw new RuntimeException("Close failed", primaryException);
}
}
}
Діагностика
- Logging — Logback/Log4j2 автоматично друкують suppressed з префіксом
Suppressed: - Unit-тести — перевіряйте
getSuppressed()при тестуванні очищення ресурсів - JSON Layout — suppressed як окреме масивне поле в ELK
- Метрики — лічіть кількість suppressed через Micrometer
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Suppressed exceptions — механізм Java 7+, дозволяє одному винятку нести список паралельних помилок
- З’явилися разом з try-with-resources — для випадків, коли
tryтаclose()обидва падають - У звичайному
try-finallysuppressed НЕ додаються автоматично — виняток зfinallyзатьмаритьtry addSuppressed()можна викликати вручну для batch-обробки помилок- Self-suppression (
e.addSuppressed(e)) →IllegalArgumentException - Suppressed масив ініціалізується ліниво — тільки при першому
addSuppressed - Можна вимкнути:
new Exception(msg, cause, false, true)—enableSuppression = false
Часті уточнюючі запитання:
- Як отримати suppressed? —
e.getSuppressed()повертаєThrowable[] - Коли suppressed НЕ працюють? — Ручний try-finally,
enableSuppression = false, тільки один виняток - Навіщо потрібні в batch-обробці? — Збирають усі помилки пачки в одному
BatchException - Чи є memory overhead? — Так, кожен suppressed = об’єкт + стек-трейс, небезпечний для синглтонів
Червоні прапорці (НЕ говорити):
- “Suppressed працюють у звичайному try-finally” — ні, тільки в try-with-resources або вручну
- “Не важливо, скільки suppressed накопичувати” — memory leak у довгоживучих об’єктах
- “Suppressed замінюють нормальну обробку помилок” — ні, це інфраструктурний механізм
- “Серіалізація suppressed безкоштовна” — увесь стек-трейс кожного suppressed летить по мережі
Пов’язані теми:
- [[23. Що станеться, якщо виняток виникне ще й в блоці finally]] — затьмарення без suppressed
- [[9. Що таке try-with-resources]] — основний сценарій використання
- [[19. Що таке загортання винятків]] — cause vs suppressed
- [[29. Що таке chaining винятків]] — cause (причина) vs suppressed (паралельна помилка)
- [[11. Що таке AutoCloseable інтерфейс]] — ресурси з suppressed при закритті