Когда код летит в стену, первым к тебе прилетает исключение. И если ты не понимаешь, кто его родственники, откуда оно взялось и чего от тебя хочет — отлаживать приложение превращается в охоту на привидений.
Java не просто так ввела чёткую иерархию исключений. Она помогает JVM грамотно обрабатывать ошибки, а тебе — структурировать код, не скатываясь в «try-catch всё подряд».
Java не просто так ввела чёткую иерархию исключений. Она помогает JVM грамотно обрабатывать ошибки, а тебе — структурировать код, не скатываясь в «try-catch всё подряд».
Часть 1. Полная карта иерархии исключений
Вот краткое дерево (для визуального представления):

Часть 2. Throwable и его особенности
У него есть два поля: detailMessage и cause. Они участвуют в трассировке стека.
В Throwable уже реализованы методы:
• getMessage()
• getCause()
• printStackTrace()
Ты можешь расширить Throwable напрямую, но это почти никогда не нужно. Лучше использовать Exception или RuntimeException.
В Throwable уже реализованы методы:
• getMessage()
• getCause()
• printStackTrace()
Ты можешь расширить Throwable напрямую, но это почти никогда не нужно. Лучше использовать Exception или RuntimeException.
Часть 3. Error — не твоя зона ответственности
Часто возникает вопрос: «А можно ли поймать Error?» — технически да. Практически — нет смысла.
• VirtualMachineError — признак краха JVM.
• OutOfMemoryError, StackOverflowError — чаще всего требуют пересмотра архитектуры или настройки JVM (а не оборачивания в try-catch).
• Их не логируют в обычном логе ошибок — для них выделяют отдельные метрики (например, алерты в APM-системах).
• VirtualMachineError — признак краха JVM.
• OutOfMemoryError, StackOverflowError — чаще всего требуют пересмотра архитектуры или настройки JVM (а не оборачивания в try-catch).
• Их не логируют в обычном логе ошибок — для них выделяют отдельные метрики (например, алерты в APM-системах).
Часть 4. Checked vs Unchecked — где та грань?
Часть 5. Кастомные исключения: когда и как
Создавать свои исключения стоит, если:
• Тебе нужно чётко разграничить типы ошибок
• Ты хочешь сделать обработку понятнее и читаемее
• Стандартных классов недостаточно
Пример:
• Тебе нужно чётко разграничить типы ошибок
• Ты хочешь сделать обработку понятнее и читаемее
• Стандартных классов недостаточно
Пример:

Не стоит:
• Создавать исключение ради одного if
• Наследовать Exception, если ты не собираешься заставлять всех его обрабатывать
• Пихать всю бизнес-логику в исключения
• Создавать исключение ради одного if
• Наследовать Exception, если ты не собираешься заставлять всех его обрабатывать
• Пихать всю бизнес-логику в исключения
Часть 6. Обработка исключений по уму
Не надо так:

Это — классический антипаттерн. Почему?
• e.printStackTrace() — это не логгирование. Ни уровня, ни контекста, ни нормального вывода в журнал, особенно если ты используешь нормальный логгер (а ты должен).
• Ловить просто Exception — значит ловить всё подряд, даже то, что ловить не должен (например, InterruptedException, IllegalStateException и другие, которые лучше пробрасывать).
• Блок try слишком большой (// много кода). Это плохо, потому что непонятно, где именно может возникнуть исключение. Лучше выделить минимальный участок кода, где реально может произойти ошибка.
Надо так:
• e.printStackTrace() — это не логгирование. Ни уровня, ни контекста, ни нормального вывода в журнал, особенно если ты используешь нормальный логгер (а ты должен).
• Ловить просто Exception — значит ловить всё подряд, даже то, что ловить не должен (например, InterruptedException, IllegalStateException и другие, которые лучше пробрасывать).
• Блок try слишком большой (// много кода). Это плохо, потому что непонятно, где именно может возникнуть исключение. Лучше выделить минимальный участок кода, где реально может произойти ошибка.
Надо так:

Здесь всё красиво:
• Ловим конкретное исключение
• Даём понятный лог: что произошло, где, и почему это важно
• Делаем разумную реакцию (уведомляем пользователя)
И немного про multi-catch:
• Ловим конкретное исключение
• Даём понятный лог: что произошло, где, и почему это важно
• Делаем разумную реакцию (уведомляем пользователя)
И немного про multi-catch:

• Мы ловим несколько логически схожих исключений в одном блоке — удобно и читаемо.
• В логгировании помимо e.getMessage() мы передаём само исключение e как последний аргумент. Это важно: если понадобится трассировка стека (stack trace), она будет доступна в логах, без дополнительных действий.
Если резюмировать: пиши try-catch осознанно. Не оборачивай “на всякий случай”, не лови всё подряд и всегда задавай себе вопрос: "А что я собираюсь делать, если здесь правда случится ошибка?"
• В логгировании помимо e.getMessage() мы передаём само исключение e как последний аргумент. Это важно: если понадобится трассировка стека (stack trace), она будет доступна в логах, без дополнительных действий.
Если резюмировать: пиши try-catch осознанно. Не оборачивай “на всякий случай”, не лови всё подряд и всегда задавай себе вопрос: "А что я собираюсь делать, если здесь правда случится ошибка?"
Часть 7. Проброс исключений (throws) — способ не захламлять код
Как мы уже говорили в части 4, checked-исключения требуют обязательной обработки. Компилятор не даст тебе пройти мимо IOException, SQLException и им подобных — ты обязан либо перехватить их, либо явно сказать: «Я не хочу с этим разбираться здесь, пусть разберутся выше».
Посмотри:
Посмотри:

Такой код не скомпилируется, если readFile() выбрасывает IOException. Ты должен сделать одно из двух:
Вариант 1 — обработать прямо здесь:
Вариант 1 — обработать прямо здесь:

Вариант 2 — пробросить дальше:

Этот второй вариант — и есть проброс исключения. Ты как бы говоришь вызывающему методу: «Я знаю, что тут может быть ошибка, но обрабатывать её должен ты».
Когда использовать throws, а не try-catch?
Если ты пишешь код в глубоком слое бизнес-логики, то есть смысл не захламлять его лишней обработкой исключений. Вместо этого пробрось ошибку наружу, а там, ближе к пользователю (например, в контроллере или сервисе верхнего уровня) уже разберись, как на неё реагировать: показать ошибку, залогировать, вернуть код ответа и т.д.
Пример:
Когда использовать throws, а не try-catch?
Если ты пишешь код в глубоком слое бизнес-логики, то есть смысл не захламлять его лишней обработкой исключений. Вместо этого пробрось ошибку наружу, а там, ближе к пользователю (например, в контроллере или сервисе верхнего уровня) уже разберись, как на неё реагировать: показать ошибку, залогировать, вернуть код ответа и т.д.
Пример:

Главное — не прятать ошибки, а передавать ответственность
throws — не способ избежать исключений, а инструмент, чтобы не решать проблему там, где ты не можешь решить её качественно. Это особенно полезно в больших проектах, где каждый слой отвечает только за свою часть логики.
throws — не способ избежать исключений, а инструмент, чтобы не решать проблему там, где ты не можешь решить её качественно. Это особенно полезно в больших проектах, где каждый слой отвечает только за свою часть логики.
Часть 8. Исключения в функциональном коде (Stream, lambda)
Сложная тема — в лямбдах нельзя бросать checked exceptions напрямую.

Варианты:
• Обернуть в RuntimeException
• Использовать вспомогательные библиотеки (например, Vavr)
• Делать врапперы с try-catch внутри
• Обернуть в RuntimeException
• Использовать вспомогательные библиотеки (например, Vavr)
• Делать врапперы с try-catch внутри
Часть 9. Исключения и архитектура: где место?
• Не кидай исключения там, где можно вернуть нормальный результат (например, Optional или Result-like объект)
• Используй исключения, чтобы прервать выполнение, когда это реально исключительная ситуация
• В REST API пробрасывай бизнес-ошибки до слоя контроллеров и возвращай понятный клиенту ответ (400, 404, 409, а не 500 на всё подряд)
• Используй исключения, чтобы прервать выполнение, когда это реально исключительная ситуация
• В REST API пробрасывай бизнес-ошибки до слоя контроллеров и возвращай понятный клиенту ответ (400, 404, 409, а не 500 на всё подряд)
Иерархия исключений в Java — это не абстрактная конструкция, а инструмент. Когда ты понимаешь её принципы, писать надёжный, чистый и понятный код становится проще. Да, можно обойтись RuntimeException и жить как будто всё хорошо — но только до тех пор, пока приложение не упадёт на проде, а лог покажет «NullPointerException at line 42». А дальше — боль, кофе и глубокое погружение в стек.
Хотите узнать больше? Изучите другие статьи из раздела:
Хотите узнать больше? Изучите другие статьи из раздела: