Что такое suppressed exceptions?
Чаще всего — в 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или реактивных стримах исключения из разных потоков не aggregируются через 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 летит по сети
Связанные темы:
- [[22. Что произойдёт, если в блоке finally тоже возникнет исключение]] — затмение без suppressed
- [[9. Что такое try-with-resources]] — основной сценарий использования
- [[18. Что такое оборачивание (wrapping) исключений]] — cause vs suppressed
- [[28. Что такое exception chaining]] — cause (причина) vs suppressed (параллельная ошибка)
- [[11. Что такое AutoCloseable интерфейс]] — ресурсы с suppressed при закрытии