Spring WebFlux에서 예외는 비동기 스트림 환경에서 다뤄야 하므로 기존 MVC와는 다른 패턴과 함수가 필요합니다. 본 포스트는 WebFlux에서 임의 예외를 발생시키는 방법과, 실무에서 자주 쓰는 예외 핸들링 함수(onErrorReturn, onErrorResume, doOnError 등)를 Kotlin 코드와 함께 상세하게 설명합니다. 실전 예제, 구조적 설명, 실무 팁, 공식 문서 및 참고자료까지 모두 포함합니다.

WebFlux에서 예외란 무엇인가?

Spring WebFlux는 리액티브 스트림(Flux, Mono) 기반의 논블로킹 환경이므로, 예외 발생과 처리가 동기 방식과 다릅니다. try-catch가 아닌 스트림 연산자(onErrorReturn, onErrorResume 등)를 활용해 예외를 처리하며, 컨트롤러/핸들러/라우터 레벨에서 다양한 방식으로 예외를 핸들링할 수 있습니다.

WebFlux 예외의 주요 특징
  • 예외가 발생해도 스트림은 멈추지 않고, 대체 데이터 또는 새로운 스트림으로 이어질 수 있음
  • try-catch는 스트림 내부에서 동작하지 않음
  • 예외 발생 시 onError 계열 연산자를 통해 처리
  • 글로벌 예외 처리(ControllerAdvice)도 지원

임의 예외 발생 방법

WebFlux에서 예외를 임의로 발생시키는 대표적인 방법은 다음과 같습니다.

// 1. map, flatMap 내부에서 예외 발생
Flux.range(1, 5)
    .map { if (it == 3) throw IllegalArgumentException("3은 허용되지 않음") else it }
    .subscribe(
        { println("onNext: $it") },
        { error -> println("onError: ${error.message}") }
    )

// 2. Mono.error/Flux.error로 명시적 예외 스트림 생성
val errorMono = Mono.error<String>(RuntimeException("강제 예외 발생!"))
errorMono.subscribe(
    { println("onNext: $it") },
    { error -> println("onError: ${error.message}") }
)
실전 예시: 임의 예외 발생 컨트롤러
@RestController
@RequestMapping("/api/error")
class ErrorDemoController {
    @GetMapping("/fail")
    fun fail(): Mono<String> = Mono.error(RuntimeException("임의 에러 발생!"))

    @GetMapping("/random")
    fun random(): Mono<String> = Mono.just((1..5).random())
        .map { if (it == 3) throw IllegalStateException("3은 지원하지 않음") else "ok: $it" }
}

WebFlux 예외 핸들링 함수들

1. onErrorReturn
  • 예외 발생 시 지정한 기본값으로 대체
Flux.just(1, 2, 0)
    .map { 10 / it }
    .onErrorReturn(-1)
    .subscribe { println("결과: $it") } // 10, 5, -1 출력
2. onErrorResume
  • 예외 발생 시 대체 스트림(Flux/Mono)으로 전환
Mono.just("test")
    .flatMap { Mono.error<String>(RuntimeException("실패!")) }
    .onErrorResume { e ->
        println("에러 발생: ${e.message}")
        Mono.just("대체 데이터")
    }
    .subscribe { println("최종 결과: $it") }
3. doOnError
  • 예외 발생 시 로그 출력, 알림 등 부가 작업 수행(스트림 종료X)
Flux.just("A", null, "C")
    .map { it!!.lowercase() }
    .doOnError { e -> println("에러 로그: ${e.message}") }
    .onErrorReturn("에러 발생시 기본값")
    .subscribe { println("결과: $it") }
4. onErrorMap
  • 예외를 다른 예외로 변환(예: 사용자 정의 예외)
Mono.just("fail")
    .flatMap { Mono.error<String>(IllegalArgumentException("잘못된 입력")) }
    .onErrorMap { e -> CustomException("사용자 정의: ${e.message}") }
    .subscribe(
        { println("onNext: $it") },
        { error -> println("onError: ${error.message}") }
    )

class CustomException(message: String): RuntimeException(message)

실전: 예외 핸들링 전체 예제 (Kotlin)

@RestController
@RequestMapping("/api/handle")
class ExceptionController {
    @GetMapping("/safe-div")
    fun safeDiv(@RequestParam a: Int, @RequestParam b: Int): Mono<String> =
        Mono.just(b)
            .map { a / it }
            .map { "결과: $it" }
            .onErrorResume { e -> Mono.just("0으로 나눌 수 없습니다: ${e.message}") }

    @GetMapping("/user/{id}")
    fun getUser(@PathVariable id: Int): Mono<String> =
        Mono.just(id)
            .flatMap { if (it == 0) Mono.error(IllegalArgumentException("ID 0은 허용 안됨")) else Mono.just("User$it") }
            .onErrorReturn("존재하지 않는 사용자")
}
예제 설명
  • /safe-div: 0으로 나눌 때 onErrorResume으로 에러 메시지 반환
  • /user/{id}: ID가 0이면 onErrorReturn으로 기본 메시지 반환

글로벌 예외 처리 (ControllerAdvice)

WebFlux에서도 @RestControllerAdvice로 글로벌 예외 처리가 가능합니다.

@RestControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(Exception::class)
    fun handle(e: Exception): ResponseEntity<String> =
        ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("글로벌 에러: ${e.message}")
}

예외 핸들링 함수별 비교 표

함수명 설명 예시 코드
onErrorReturn 예외 발생 시 기본값 반환 .onErrorReturn(-1)
onErrorResume 예외 발생 시 대체 스트림 반환 .onErrorResume { ... }
doOnError 예외 발생 시 부가 작업 .doOnError { ... }
onErrorMap 예외 변환 .onErrorMap { ... }

WebFlux 예외 핸들링 실전 팁

  • try-catch가 아닌 onError 계열 연산자를 적극 활용
  • 예외 발생 시 로그, 모니터링, 알림 연동 등 부가 작업은 doOnError에서 처리
  • onErrorResume으로 에러 상황에 맞는 대체 데이터/스트림 제공
  • 글로벌 예외 처리는 RestControllerAdvice로 일관성 있게 관리
  • 예외 메시지 노출은 보안상 주의(내부 정보 노출X)

결론 및 도움말

WebFlux에서 예외 처리는 스트림 연산자를 적극적으로 활용하는 것이 핵심입니다. 실전에서는 onErrorReturn, onErrorResume, doOnError 등을 적절히 조합해, 안정적이고 예측 가능한 리액티브 서버를 구현할 수 있습니다. 공식 문서와 다양한 샘플 코드를 참고하여, 자신만의 예외 처리 패턴을 만들어보세요.

레퍼런스