Почему не стоит глотать исключения (catch empty)?
Это перехват исключения без каких-либо действий:
Junior Level
Что такое “глотание” исключений
Это перехват исключения без каких-либо действий:
try {
processOrder(order);
} catch (Exception e) {
// Пусто — исключение "проглочено"
}
// Программа продолжает работу как ни в чём не бывало
Механизм: почему исключение теряется
Когда JVM выполняет catch блок, она снимает исключение со стека обработки ошибок. Если блок catch пустой:
- JVM создаёт объект исключения (это уже стоило CPU-циклов на
fillInStackTrace()) - Заходит в
catchблок — там ничего нет - Продолжает выполнение после
catch— объект исключения становится недостижимым и будет собран GC - Ни один код выше по стеку не узнает об ошибке — метод, который вызвал
processOrder(), думает, что всё прошло успешно
Это как звонок в дверь, который вы услышали, но решили не открывать — звонивший ушёл, а вы даже не узнали, кто это был.
Почему это плохо
Когда исключение глотается, программа переходит в неконсистентное состояние:
try {
connection.beginTransaction();
connection.save(order); // SQLException!
connection.commit(); // Выполнится — часть данных потеряна
} catch (SQLException e) {
// Пусто — транзакция коммитится с неполными данными
}
Итог: часть данных записана, часть нет. Бизнес-логика нарушена. В логах — тишина.
Что делать вместо этого
// Минимум — залогогировать
try {
processOrder(order);
} catch (Exception e) {
log.error("Failed to process order", e);
}
// Лучше — пробросить дальше
try {
processOrder(order);
} catch (Exception e) {
log.error("Failed to process order", e);
throw new OrderProcessingException("Order failed", e);
}
Middle Level
Когда это ДОПУСТИМО
Всего несколько сценариев, когда пустой (или почти пустой) catch block — это нормально:
1. Закрытие ресурсов (до Java 7):
} finally {
try { if (socket != null) socket.close(); }
catch (IOException ignored) { /* Ничего нельзя сделать — сокет всё равно закроется */ }
}
Современное решение: try-with-resources (он сам обрабатывает suppressed исключения).
2. Ожидаемые прерывания при завершении:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Восстанавливаем статус прерывания — обязательно!
Thread.currentThread().interrupt();
}
Здесь catch не пустой — мы восстанавливаем interrupted flag.
3. Ignorable optional operations (например, Optional.orElseGet паттерн):
// Кэширование — если кэш недоступен, вычисляем напрямую
try {
cache.put(key, value);
} catch (CacheException e) {
// Кэш — оптимизация, а не requirement. Его недоступность — не ошибка бизнес-логики.
// Минимум — debug-лог:
log.debug("Cache unavailable, skipping", e);
}
4. Cleanup при shutdown:
try {
temporaryFile.delete();
} catch (IOException e) {
// Файл временный, ОС всё равно почистит /tmp при перезагрузке
log.debug("Failed to delete temp file {}", temporaryFile, e);
}
Ключевой принцип: если вы игнорируете исключение, убедитесь что:
- Это осознанное решение, а не лень
- Последствия ошибки не влияют на бизнес-логику
- Есть хотя бы
log.debug()для отладки
Когда НЕ использовать пустой catch
- Бизнес-логика — практически никогда. Даже
log.debug()лучше пустоты, потому что даёт нитку для отладки. Единственное исключение: если бизнес-спецификация явно говорит «игнорировать эту ошибку» — но тогда всё равно логируйте на DEBUG - В транзакциях — проглоченный
SQLExceptionприведёт к коммиту частичных данных - В stream API —
nullв коллекции вместо ошибки молча сломает downstream - В асинхронном коде —
CompletableFutureс проглоченным исключением никогда не завершится exceptionally - В middleware — проглоченное исключение вернёт клиенту
200 OKвместо ошибки
Hidden Latency
Глотание исключений скрывает проблемы производительности. Сервис падает по таймауту, ошибки глотаются — вы видите просто медленную работу без понимания причин.
CPU Spikes
Частое возникновение и глотание исключений всё равно нагружает CPU на fillInStackTrace(). “Тихое” исключение — не значит “бесплатное”.
Functional Streams
Глотание внутри forEach или map — коллекция с неполными данными без предупреждения:
list.stream()
.map(item -> {
try {
return process(item);
} catch (Exception e) {
// Проглотили — результат null
return null;
}
})
.collect(Collectors.toList()); // [result1, null, result3, null]
Global Exception Handler
Если проглотили исключение в середине цепочки, @ControllerAdvice в Spring вернёт клиенту 200 OK — обман API.
Senior Level
Quiet Failure и зомби-системы
Проглоченное исключение превращает систему в “зомби” — работает, но некорректно. Без механизма самовосстановления это катастрофа.
Static Analysis
Правила SonarQube (“Exceptions should not be ignored”) и IntelliJ Inspections (“Empty catch block”) должны быть блокирующими в CI/CD.
Log at Least as DEBUG
Если исключение можно проигнорировать — логируйте хотя бы на уровне DEBUG:
try {
optionalResource.close();
} catch (IOException e) {
log.debug("Failed to close optional resource", e);
}
Это спасёт часы отладки.
Self-Healing Systems
Если игнорируете ошибку — должен быть механизм самовосстановления:
try {
cache.put(key, value);
} catch (CacheException e) {
log.warn("Cache unavailable, falling back to direct computation", e);
return computeDirectly(); // Самовосстановление
}
Metric-driven detection
try {
processOrder(order);
} catch (Exception e) {
meterRegistry.counter("exceptions.swallowed", "type", e.getClass().getSimpleName()).increment();
log.debug("Swallowed exception", e);
}
Диагностика
- SonarQube — блокирует пустые catch-блоки
- IntelliJ Inspections — подсвечивает
catch (Exception e) {} - Prometheus/Grafana — алертинг на аномалии в swallowed exceptions
- Thread Dumps — если поток “завис” после проглоченного исключения
🎯 Шпаргалка для интервью
Обязательно знать:
- Глотание исключений переводит программу в неконсистентное состояние без записи в логи
- Пустой
catchвсё равно тратит CPU наfillInStackTrace()— “тихое” исключение не бесплатное - Допустимые сценарии: закрытие ресурсов (до Java 7),
InterruptedExceptionс восстановлением флага, опциональный кэш - Минимум —
log.debug()даже при осознанном игнорировании - В транзакциях проглоченный
SQLException= коммит частичных данных - В async-коде проглоченное исключение =
CompletableFutureникогда не завершится exceptionally - SonarQube и IntelliJ Inspections должны блокировать пустые catch-блоки в CI/CD
Частые уточняющие вопросы:
- Когда МОЖНО проглотить исключение? — Опциональные операции (кэш), cleanup при shutdown, но минимум с
log.debug() - Что делать с
InterruptedException? — Восстановить флаг:Thread.currentThread().interrupt() - Почему в транзакциях это катастрофа? — Проглоченная ошибка = частичный коммит, данные повреждены
- Как обнаружить проблему в production? — SonarQube rules, Prometheus метрики swallowed exceptions, thread dumps
Красные флаги (НЕ говорить):
- “У меня везде пустые catch, если ошибка не критична” — это зомби-система
- “Глотаю исключения в бизнес-логике” — никогда не допустимо
- “Пустой catch не влияет на производительность” —
fillInStackTrace()стоит CPU-циклов - “Мне не нужны логи, я знаю что ошибка случается” — без логов нет отладки
Связанные темы:
- [[17. Как правильно логировать исключения]] — логирование как минимум на DEBUG
- [[23. Что такое suppressed exceptions]] — TWR сохраняет обе ошибки
- [[22. Что произойдёт, если в блоке finally тоже возникнет исключение.md]] — потеря ошибки в finally
- [[24. Можно ли несколько catch блоков для одного try]] — правильная обработка разных типов