Spring Boot 3 + Kotlin에서 MVC와 WebFlux의 글로벌 에러 핸들링 완벽 가이드
Spring Boot 3와 Kotlin 환경에서 MVC와 WebFlux 각각의 글로벌(중앙) 에러 핸들링 방법을 총정리합니다. 실무에서 자주 마주치는 에러 처리 패턴과 실전 적용 방법을 초보자도 쉽게 이해할 수 있도록 설명합니다. MVC와 WebFlux의 차이점, 각 방식의 장단점, 그리고 코드 예시까지 한 번에 정리합니다.
들어가며
Spring Boot 3는 최신 자바와 코틀린 환경에서 더욱 강력한 기능을 제공합니다. 웹 애플리케이션을 개발하다 보면 예외 상황을 효과적으로 처리하는 것이 매우 중요합니다. 특히, REST API나 웹 서비스에서 일관된 에러 응답을 제공하는 것은 사용자 경험과 유지보수성에 큰 영향을 미칩니다.
Spring Boot에서는 크게 두 가지 웹 프레임워크(MVC, WebFlux)를 지원하며, 각각의 에러 핸들링 방식이 다릅니다. 본 포스트에서는 Spring Boot 3 + Kotlin 환경에서 MVC와 WebFlux 각각의 글로벌 에러 핸들링 방법을 상세히 소개합니다.
1. Spring Boot 3 + Kotlin 환경 준비
Spring Boot 3와 Kotlin을 함께 사용하는 환경은 build.gradle.kts 또는 pom.xml에 spring-boot-starter-web, spring-boot-starter-webflux, kotlin 관련 의존성을 추가하면 쉽게 구성할 수 있습니다.
// build.gradle.kts 예시
plugins {
id("org.springframework.boot") version "3.0.0"
id("io.spring.dependency-management") version "1.0.15.RELEASE"
kotlin("jvm") version "1.9.0"
kotlin("plugin.spring") version "1.9.0"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web") // MVC
implementation("org.springframework.boot:spring-boot-starter-webflux") // WebFlux
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
2. Spring MVC에서의 글로벌 에러 핸들링
2-1. @ControllerAdvice와 @ExceptionHandler
Spring MVC에서는 @ControllerAdvice
와 @ExceptionHandler
를 활용해 전역적으로 예외를 처리할 수 있습니다. 이 방식은 동기 방식의 전통적인 웹 애플리케이션에 적합합니다.
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(CustomException::class)
fun handleCustomException(ex: CustomException): ResponseEntity<ErrorResponse> {
val response = ErrorResponse(
code = ex.errorCode,
message = ex.message ?: "알 수 없는 오류가 발생했습니다."
)
return ResponseEntity.status(ex.status).body(response)
}
@ExceptionHandler(Exception::class)
fun handleException(ex: Exception): ResponseEntity<ErrorResponse> {
val response = ErrorResponse(
code = "INTERNAL_ERROR",
message = ex.localizedMessage ?: "서버 내부 오류가 발생했습니다."
)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response)
}
}
// 예시용 커스텀 예외 및 응답 클래스
class CustomException(val errorCode: String, val status: HttpStatus, message: String): RuntimeException(message)
data class ErrorResponse(val code: String, val message: String)
2-2. HandlerExceptionResolver
보다 세밀한 제어가 필요하다면 HandlerExceptionResolver
를 구현할 수도 있습니다. 하지만 실무에서는 대부분 @ControllerAdvice
를 활용하는 것이 유지보수에 유리합니다.
3. Spring WebFlux에서의 글로벌 에러 핸들링
WebFlux는 비동기/논블로킹 환경을 지원하는 리액티브 웹 프레임워크입니다. MVC와는 다른 방식으로 예외를 처리해야 하며, 대표적으로 @ControllerAdvice
와 @ExceptionHandler
도 지원하지만, 내부적으로는 Mono/Flux 기반의 리턴 타입을 사용해야 합니다.
3-1. @ControllerAdvice + Mono/Flux
@RestControllerAdvice
class GlobalWebFluxExceptionHandler {
@ExceptionHandler(CustomException::class)
fun handleCustomException(ex: CustomException): Mono<ResponseEntity<ErrorResponse>> {
val response = ErrorResponse(
code = ex.errorCode,
message = ex.message ?: "알 수 없는 오류가 발생했습니다."
)
return Mono.just(ResponseEntity.status(ex.status).body(response))
}
@ExceptionHandler(Exception::class)
fun handleException(ex: Exception): Mono<ResponseEntity<ErrorResponse>> {
val response = ErrorResponse(
code = "INTERNAL_ERROR",
message = ex.localizedMessage ?: "서버 내부 오류가 발생했습니다."
)
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response))
}
}
3-2. WebExceptionHandler 구현
WebFlux에서 더 낮은 레벨의 글로벌 에러 핸들링이 필요하다면 WebExceptionHandler
를 직접 구현할 수 있습니다.
@Component
class CustomWebExceptionHandler : WebExceptionHandler {
override fun handle(
exchange: ServerWebExchange,
ex: Throwable
): Mono<Void> {
val response = exchange.response
response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR
response.headers.contentType = MediaType.APPLICATION_JSON
val errorBody = ErrorResponse(
code = "INTERNAL_ERROR",
message = ex.localizedMessage ?: "WebFlux 서버 오류 발생"
)
val buffer = response.bufferFactory().wrap(
ObjectMapper().writeValueAsBytes(errorBody)
)
return response.writeWith(Mono.just(buffer))
}
}
4. MVC vs WebFlux 에러 핸들링 차이점 정리
구분 | Spring MVC | Spring WebFlux |
---|---|---|
주요 어노테이션 | @ControllerAdvice, @ExceptionHandler | @ControllerAdvice, @ExceptionHandler, WebExceptionHandler |
리턴 타입 | ResponseEntity | Mono<ResponseEntity |
동작 방식 | 동기 | 비동기/논블로킹 |
커스텀 핸들러 | HandlerExceptionResolver | WebExceptionHandler |
활용 예 | REST API, 웹사이트 | 실시간 데이터 처리, 스트리밍 |
- MVC는 동기 방식이므로 예외 처리 로직이 직관적이고 단순합니다.
- WebFlux는 비동기/논블로킹 특성 때문에 Mono/Flux를 리턴해야 하며, 커스텀 핸들러 구현이 더 자주 필요할 수 있습니다.
5. 실전 적용 팁 및 베스트 프랙티스
- 공통 응답 포맷을 정의하여 일관성 있는 에러 메시지 제공
- 커스텀 예외 계층을 설계하여 다양한 비즈니스 에러를 구분
- 로그를 남길 때는 민감 정보가 노출되지 않도록 주의
- WebFlux에서는 Mono/Flux 리턴을 잊지 말 것
- 테스트 코드로 에러 상황을 반드시 검증
- 필요하다면 AOP로 로깅/트래킹 기능 추가
6. 참고 코드 저장소 및 공식 문서
Spring Boot 3 + Kotlin 환경에서 MVC와 WebFlux 모두 글로벌 에러 핸들링을 통해 일관된 API 응답과 유지보수성을 확보할 수 있습니다. 각 방식의 특성을 이해하고, 실무에 맞게 적용해보세요!