Объясните механизм: почему catch не перехватывает исключение только потому, что его тип указан в cause?
catch проверяет тип фактически выброшенного объекта, а не просматривает его цепочку причин. Поэтому обработчик срабатывает только тогда, когда выброшенное исключение совместимо по типу с параметром catch; тип объекта в cause сам по себе не влияет на выбор обработчика.
cause предназначен для диагностики и преобразования исключений между слоями приложения. Чтобы обработать исходную причину, её нужно явно извлечь и проверить либо перехватить исключение внешнего типа, если именно оно было выброшено.
В прикладных системах ошибка часто проходит через несколько слоёв: драйвер сообщает техническую проблему, репозиторий преобразует её в инфраструктурное исключение, а сервис — в доменное. Механизм цепочки причин позволяет сохранить исходное исключение при таком преобразовании, не смешивая интерфейсы разных слоёв.
В Java цепочка причин поддерживается объектом Throwable: обёртка хранит ссылку на исходную ошибку, но эта ссылка является диагностической информацией. Она не изменяет правила поиска обработчика и не заставляет catch рекурсивно обходить причины.
Предположим, низкоуровневый метод выбросил ФайлНеНайден, а вызывающий слой создал ОшибкаКонфигурации, передав исходную ошибку как причину. Если наружу выброшена именно ОшибкаКонфигурации, обработчик catch (ФайлНеНайден) не сработает.
Неверное ожидание особенно опасно при миграции API или добавлении слоя-обёртки: после изменения типа внешнего исключения существующие обработчики могут перестать выполняться. При этом исходная ошибка не потеряна — она доступна через getCause() и обычно присутствует в трассировке стека.
Выбор обработчика происходит по динамическому типу исключения, переданного в throw. Java проверяет обработчики текущего блока try сверху вниз и выбирает первый, параметр которого совместим с фактическим объектом исключения.
Поле cause не участвует в этом сопоставлении. Оно хранит ссылку на предшествующее исключение, поэтому в следующем примере сработает обработчик ОшибкаКонфигурации, а не ФайлНеНайден:
Результатом будет config, затем ФайлНеНайден. Для обработки исходного типа можно перехватить внешнее исключение и проверить getCause(), но такой подход требует аккуратно учитывать глубину цепочки и типы причин. Обычно предпочтительнее обрабатывать публичный тип, предназначенный для текущего слоя, а исходную причину использовать для логирования и диагностики.
Обёртка также может быть исключением-наследником общего базового типа, поэтому catch (RuntimeException) или catch (Exception) способен перехватить её. Это всё равно не означает, что обработчик автоматически перехватит любой тип из цепочки причин.
Сервис конфигурации читает файл через библиотеку, которая выбрасывает несколько технических исключений. Команда решила не раскрывать библиотечные типы наружу и преобразовала их в ОшибкаКонфигурации, сохранив исходную ошибку как причину.
Рассматривались два варианта. Первый — оставить технические исключения без обёртки: это упрощает обработку конкретных причин, но связывает бизнес-слой с библиотекой. Второй — проверять всю цепочку cause в каждом вызывающем месте: это сохраняет гибкость, но дублирует логику и делает поведение зависимым от глубины обёрток.
Выбрали обработку ОшибкаКонфигурации на границе сервиса, а исходную причину передавали в журнал и систему мониторинга. Это изолировало библиотеку, сохранило диагностические данные и сделало контракт сервиса стабильным.
Изменится ли поведение, если исходная ошибка является подклассом типа обработчика?
Да, catch учитывает наследование фактически выброшенного объекта. Если выброшен объект класса ОшибкаКонфигурации, а обработчик принимает её родителя Exception, он сработает. Но наличие подходящего родственного типа только в cause по-прежнему ничего не меняет.
Можно ли перехватить исходную ошибку через catch, указав тип внешней обёртки?
Да, если внешняя обёртка действительно была выброшена. Внутри такого обработчика можно получить причину через getCause(), проверить её тип и при необходимости принять решение о повторном выбросе. Однако это уже явная логика приложения, а не автоматический механизм сопоставления исключений.
Влияет ли подавленное исключение на выбор обработчика?
Нет. Подавленные исключения, доступные через getSuppressed(), используются главным образом для диагностики, например когда ошибка закрытия ресурса добавляется к основной ошибке. catch сопоставляет только фактически выброшенный объект; ни cause, ни suppressed-исключения не расширяют поиск подходящего обработчика.