Можно ли повторно бросить исключение?
Повторный бросок (rethrow) — перехват исключения в catch с целью совершить действие (логирование) и отправка дальше:
Junior Level
Да, можно!
Повторный бросок (rethrow) — перехват исключения в catch с целью совершить действие (логирование) и отправка дальше:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void processOrder(Long orderId) {
try {
orderRepository.save(orderId);
} catch (Exception e) {
log.error("Failed to process order id={}", orderId, e); // Логируем
throw e; // Пробрасываем дальше
}
}
}
Зачем это нужно
- Логирование — записать ошибку перед тем, как она уйдёт дальше
- Метрики — посчитать количество ошибок
- Очистка — освободить ресурсы перед пробросом
try {
connection.beginTransaction();
connection.save(order);
} catch (SQLException e) {
connection.rollback(); // Откатываем транзакцию
throw e; // Пробрасываем дальше
}
Обёртка при повторном броске
Часто оборачивают в другое исключение:
try {
repository.save(order);
} catch (SQLException e) {
throw new OrderException("Failed to save order id=" + order.getId(), e); // Новый тип + контекст
}
Когда НЕ использовать rethrow
- Без добавления ценности —
catch (Exception e) { throw e; }бесполезен без логирования/метрик - Дублирование логирования — если исключение уже залогировано на этом уровне, не логируйте повторно при rethrow
- Вместо оборачивания — если нужен новый тип исключения, используйте wrapping с cause, а не голый rethrow
- В транзакциях без rollback — rethrow unchecked исключения в
@Transactionalоткатит транзакцию, checked — нет - Многократный rethrow — не ловите/бросайте одно и то же исключение на каждом слое — достаточно одного уровня
Middle Level
Инструкция athrow
На уровне байт-кода повторный бросок — обычная инструкция athrow. Она берёт объект исключения из стека операндов и ищет обработчик в Exception Table.
Важно: при throw e; стек-трейс не меняется. В нём будет строка оригинального возникновения, а не строка throw e.
Precise Rethrow (Java 7+)
Одна из самых полезных, но редко обсуждаемых функций:
public void process() throws IOException, SQLException {
try {
if (condition) throw new IOException();
else throw new SQLException();
} catch (Exception e) { // Ловим общий предок
log.error("Intermediary processing");
throw e; // Компилятор "знает" — только IOException или SQLException
}
}
До Java 7 пришлось бы объявить throws Exception. Теперь компилятор анализирует содержимое try.
Rethrow с оборачиванием (Exception Chaining)
// Плюс: добавляем контекст текущего слоя
throw new MyException("Context: processing order " + orderId, e);
// Минус: новый объект + новый fillInStackTrace()
// Всегда передавайте оригинальное исключение как cause!
Side Effects в rethrow
В высоконагруженных системах rethrow используется для метрик:
catch (RuntimeException e) {
metrics.increment("errors.count");
throw e;
}
Senior Level
Double Traversal
Повторный бросок заставляет JVM заново сканировать Exception Table. В глубоких иерархиях частые rethrow создают небольшую нагрузку на CPU.
Checked to Unchecked
Часто checked исключения пробрасываются как unchecked — “разрывает” обязательства в сигнатуре:
try {
// checked exception
Files.readAllLines(path);
} catch (IOException e) {
throw new RuntimeException(e); // Unchecked — метод больше не объявляет throws
}
Sneaky Throws
Трюк с пробросом checked исключения без объявления в throws:
@SuppressWarnings("unchecked")
private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e;
}
Lombok @SneakyThrows использует этот же механизм.
Losing Exceptions
Если в блоке catch вызвали метод, который сам бросил исключение, и не пробросили оригинал — оригинал потерян:
catch (Exception e) {
log.info("Error"); // OK
doSomethingThatAlsoThrows(); // Новое исключение! Оригинал e потерян!
throw e; // Никогда не выполнится
}
fillInStackTrace() — обновление стека
Если хотите обновить стек-трейс текущей строкой (чтобы в логах было видно место rethrow):
throw (RuntimeException) e.fillInStackTrace();
Делайте это только если понимаете зачем — оригинальный стек-трейс будет потерян.
Диагностика
- Log Analysis — убедитесь, что каждый rethrow логируется с контекстом
- Метрики — считайте rethrow через Micrometer
- Debugger — в IntelliJ разверните узел
causeдля просмотра всей цепочки javap -c— покажет инструкциюathrowв байт-коде
🎯 Шпаргалка для интервью
Обязательно знать:
- Rethrow — перехват исключения в
catchс действием (лог, метрики, rollback) и проброс дальше черезthrow e - При
throw e;стек-трейс не меняется — показывает оригинальное место возникновения - Checked исключения можно пробросить без потери типизации благодаря Precise Rethrow (Java 7+)
- Обёртка при rethrow добавляет контекст:
throw new OrderException("msg", e)— всегда передавайтеcause - Rethrow unchecked в
@Transactionalоткатит транзакцию, checked — нет - Бесполезный rethrow:
catch (Exception e) { throw e; }без логирования/метрик/очистки e.fillInStackTrace()обновляет стек-трейс — используйте осознанно, оригинальный стек будет потерян
Частые уточняющие вопросы:
- Меняется ли стек-трейс при
throw e? — Нет, показывает оригинальное место ошибки - Что такое Precise Rethrow? — Java 7+: компилятор анализирует
tryи сохраняет точные типы вthrows - Когда rethrow бесполезен? — Без логирования, метрик, очистки — голый
catch { throw e; } - Как обновить стек-трейс? —
e.fillInStackTrace()— но оригинальный стек будет потерян
Красные флаги (НЕ говорить):
- “При rethrow стек-трейс обновляется” — нет, только при
fillInStackTrace() - “Rethrow checked исключения не откатывает
@Transactional” — зависит от типа: unchecked откатит, checked — нет - “Многократный rethrow на каждом слое — хорошая практика” — достаточно одного уровня
- “Rethrow без cause теряет оригинальную ошибку” — при
throw eбез обёртки ошибка не теряется
Связанные темы:
- [[18. Что такое оборачивание (wrapping) исключений]] — обёртка при rethrow
- [[20. Что делает ключевое слово throws]] — Precise Rethrow сохраняет типы
- [[28. Что такое exception chaining]] — chaining при обёртке с cause
- [[21. Можно ли пробросить checked exception из метода без throws]] — sneaky throws альтернатива
- [[17. Как правильно логировать исключения]] — логирование перед rethrow