middle
Как обрабатывать ошибки в реактивных потоках?
В реактивном программировании ошибка — терминальный сигнал, после которого поток прекращает работу. Project Reactor предоставляет набор операторов для перехвата, замены и восстановления после ошибок.
Основные операторы обработки ошибок
| Оператор | Назначение | Пример |
|---|---|---|
onErrorReturn |
Заменить ошибку значением по умолчанию | flux.onErrorReturn(-1) |
onErrorResume |
Заменить ошибку альтернативным потоком | mono.onErrorResume(e -> fallback()) |
onErrorMap |
Преобразовать ошибку в другой тип | mono.onErrorMap(e -> new ApiException(e)) |
doOnError |
Побочный эффект (логирование) без перехвата | mono.doOnError(e -> log.error(...)) |
retry / retryWhen |
Повторная попытка подписки | mono.retry(3) |
onErrorComplete |
Преобразовать ошибку в сигнал завершения | flux.onErrorComplete() |
Пример
// onErrorReturn — значение по умолчанию
Flux.just(1, 2, 0)
.map(i -> 10 / i)
.onErrorReturn(-1)
.subscribe(System.out::println);
// Вывод: 10, 5, -1
Пример
// onErrorResume — разная обработка для разных типов ошибок
Mono<User> user = userService.findById(id)
.onErrorResume(NotFoundException.class, e -> Mono.empty())
.onErrorResume(ServiceException.class, e -> fallbackService.findById(id));
Пример
// onErrorMap — преобразование типа ошибки
Mono<User> user = userRepository.findById(id)
.onErrorMap(DataAccessException.class,
e -> new ServiceException("Ошибка БД", e));
Пример: retryWhen с backoff
Mono<Response> response = webClient.get()
.uri("/api/data")
.retrieve()
.bodyToMono(Response.class)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.maxBackoff(Duration.ofSeconds(10))
.filter(e -> e instanceof WebClientResponseException.ServiceUnavailable)
.onRetryExhaustedThrow((spec, signal) ->
new ServiceException("Сервис недоступен после 3 попыток")));
Пример: комбинированная обработка ошибок
Mono<OrderDto> result = orderService.createOrder(request)
.doOnError(e -> log.error("Ошибка создания заказа", e))
.onErrorMap(DataAccessException.class,
e -> new ApiException(500, "Ошибка базы данных"))
.onErrorMap(ValidationException.class,
e -> new ApiException(400, e.getMessage()))
.retryWhen(Retry.backoff(2, Duration.ofMillis(500))
.filter(e -> e instanceof TransientDataAccessException));
Частые ошибки
onErrorReturnв середине цепочки — поток завершится после возврата значения, последующие элементы Flux не будут эмитированы- Бесконечный
retryбез фильтра — бесконечный цикл при постоянной ошибке - Retry для неидемпотентных операций — повтор POST-запроса может создать дубликаты
- Игнорирование ошибок без
doOnError— если ошибка «проглатывается», важно хотя бы залогировать
Как используется в 2026
retryWhenсRetry.backoff— стандартный паттерн для устойчивости микросервисов- Интеграция с Resilience4j для circuit breaker + retry
- Spring WebFlux:
@ExceptionHandlerиWebExceptionHandlerдля глобальной обработки - Structured error handling через
onErrorResumeс разными типами исключений — best practice
На собеседовании: покажите знание нескольких операторов и объясните разницу: onErrorReturn заменяет значением, onErrorResume — потоком, onErrorMap — типом ошибки, doOnError — побочный эффект. Частая ошибка — не знать, что порядок операторов важен: ошибка перехватывается ближайшим оператором ниже по цепочке.