Как обрабатывать исключения в Java: прокидывать дальше или возвращать пустой список
Что проверяют
Понимаете ли вы разницу между «данных нет» и «узнать не удалось». Пустой список отвечает на первое, исключение на второе, и подмена одного другим прячет аварию. Следом спрашивают, где ошибку всё-таки ловить и превращать в ответ клиенту.
Как отвечать
Базовое правило: пустой список возвращают, когда отсутствие данных это нормальный результат. Поиск без совпадений, пользователь без заказов. Если же база недоступна или запрос упал, метод не знает ответа и должен об этом сообщить. Иначе интерфейс покажет «у вас нет заказов», а фоновая синхронизация решит, что записи удалены, и удалит их у себя.
Каркас ответа:
- пробрасывать: на уровне репозитория и сервиса. Проверяемое оборачивают в своё непроверяемое с сохранением причины:
throw new OrderLoadException("...", e); - ловить на границе: в Spring это
@RestControllerAdviceс@ExceptionHandler, который превращает исключение в 500 или 503 и пишет лог один раз; - деградировать осознанно: для второстепенного блока вроде рекомендаций можно вернуть пустой список, но с логом, метрикой и явным решением, часто через fallback в Resilience4j;
- никогда не
null: для коллекций возвращаютList.of()илиCollections.emptyList(), аOptionalоставляют для одиночных значений; - явный результат: если вызывающему нужно различать исходы, подойдёт sealed-интерфейс с реализациями «успех» и «ошибка» и
switchс сопоставлением по типу (Java 21).
Стоит упомянуть транзакции: Spring по умолчанию откатывает их только на непроверяемых исключениях. Если внутренний метод с @Transactional упал и пометил общую транзакцию на откат, а внешний поймал ошибку и вернул пустой список, на коммите вылетит UnexpectedRollbackException.
Где спотыкаются
Пишут catch (Exception e) { return List.of(); } без лога. Ошибка исчезает бесследно, а потом выясняется, что отчёт неделями был пустым из-за таймаута.
Логируют и пробрасывают на каждом уровне. Одна ошибка даёт пять стектрейсов, и по логам уже не понять, сколько было сбоев.
Оборачивают исключение без причины: new ServiceException(e.getMessage()). Исходный стектрейс теряется, и место падения приходится искать вслепую.