예외 상황에서 트래킹하기 편하도록 응답 핸들러를 작성해보자

2025. 6. 18. 16:13·Project/PickLab

대부분 프로젝트를 진행하며 예외처리를 하기 위한 Exception 핸들러를 작성하게 된다.

그 형식은 프로젝트에 따라 조금씩 다르겠지만, @RestControllerAdvice 어노테이션을 통해 예외 발생 시 이를 catch 하여 프로젝트의 형식에 맞도록 응답을 공통적으로 내려주도록 관리가 된다.

private fun findActiveMember(memberId: Long): Member =
        memberRepository
            .findByIdAndDeletedAtIsNull(memberId)
            .orElseThrow { BusinessException(ErrorCode.INVALID_MEMBER) }

이 코드에서 예외가 발생한다면

@RestControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException::class)
    fun handleBusinessException(e: BusinessException): ResponseEntity<ResponseWrapper<Unit>> {
        log.warn("[handleBusinessException] ${e.message}", e)

        return ResponseEntity
            .status(e.errorCode.status)
            .body(ResponseWrapper.error(e.errorCode))
    }
}

이러한 핸들러를 통해서 예외를 catch 하고 정의한 응답을 클라이언트로 보내주게 된다.

그렇다면, 내가 정의한 직접적인 예외 말고 스프링에서 정의하게 된 예외의 경우에는 어떻게 될까?

현재 나는 컨트롤러에서 @Valid 어노테이션을 통해서 요청 객체에 대한 검증을 진행하고 있다.

data class UpdateEmailRequest(
    @field:NotBlank(message = "이메일은 필수 입력값입니다.")
    @field:Email(message = "유효하지 않은 이메일 형식입니다.")
    @field:Schema(description = "이메일", example = "test@example.com")
    val email: String,
)

이메일을 수정하기 위한 요청을 받는 객체이다. 우리가 여기서 파악할 수 있는 것은, 이 필드는 비어있으면 안되며 Email의 형식을 갖추어야 한다는 점이다.

1. email 필드가 누락되어 요청이 들어오는 경우

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class picklab.backend.member.entrypoint.request.UpdateEmailRequest] value failed for JSON property email due to missing (therefore NULL)
value for creator parameter email which is a non-nullable type]

생성자에서 email 필드를 요구하고 있지만, email 필드가 존재하지 않기 때문에 HttpMessageNotReadableException 예외가 발생한다.

2. 필드 입력값으로 "" 가 들어올 경우

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [1] in public org.springframework.http.ResponseEntity<picklab.backend.common.model.ResponseWrapper<kotlin.Unit>> picklab.backend.member.entrypoint.MemberController.changeEmail(picklab.backend.common.model.MemberPrincipal,picklab.backend.member.entrypoint.request.UpdateEmailRequest): [Field error in object 'updateEmailRequest' on field 'email': rejected value []; codes [NotBlank.updateEmailRequest.email,NotBlank.email,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [updateEmailRequest.email,email]; arguments []; default message [email]];
default message [이메일은 필수 입력값입니다.]] ]

@NotBlank에 걸리게 되어 MethodArgumentNotValidException이 발생하고 메시지로는 내가 지정해둔 메시지가 나오게 된다.

3. 올바르지 않은 이메일 형식일 경우

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [1] in public org.springframework.http.ResponseEntity<picklab.backend.common.model.ResponseWrapper<kotlin.Unit>> picklab.backend.member.entrypoint.MemberController.changeEmail(picklab.backend.common.model.MemberPrincipal,picklab.backend.member.entrypoint.request.UpdateEmailRequest): [Field error in object 'updateEmailRequest' on field 'email': rejected value [test]; codes [Email.updateEmailRequest.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [updateEmailRequest.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@6307de7,.*];
default message [유효하지 않은 이메일 형식입니다.]] ]

@Email 검증에 걸리게 되어 같은 MethodArgumentNotValidException 예외지만 다른 메시지를 보여주게 된다.

위에 설정한 예외들을 핸들링 하는 핸들러는 다음과 같다.

@ExceptionHandler(
        HttpMessageNotReadableException::class,
        MethodValidationException::class,
)
fun handleMvcException(e: Exception): ResponseEntity<ResponseWrapper<Unit>> {
    log.warn("[handleMvcException] ${e.message}", e)

    return ResponseEntity
        .status(ErrorCode.BAD_REQUEST.status)
        .body(ResponseWrapper.error(ErrorCode.BAD_REQUEST))
}

예외가 발생했을 때 로그로 어떠한 에러가 발생했는지를 남기게 되고, 공통응답을 내려주는데 모두 BAD_REQUEST 하나로 통일되어 있습니다.

즉, 전혀 다른 3개의 상황임에도 같은 메시지가 전달되기 때문에 만약 쉽게 이유를 찾을 수 없는 경우에는 디버깅을 하기 위해서 시간적인 비용이 많이 소모 될 것이라 예상된다.

변경 후의 에러 응답

우선적으로 파악했던 MethodArgumentNotValidException, HttpMessageNotReadableException(에서의 직렬화 문제) 예외에 대한 부분을 세분화 해 보았다.

<직렬화 문제>

<Validation 문제>

확실히 어떤 부분에서 에러가 발생했는지 한 눈에 보기 쉬워졌다. 이를 이용하면, 클라이언트 측에서도 api를 연동함에 있어서 더 쉽게 진행할 수 있지 않을까? 라고 생각해봤다.

 

우리는 서비스를 만들게 되면서 많은 에러 상황에 놓이게 된다. 에러를 자세하게 클라이언트 측에 안내하는 것도 좋지만, DB 컬럼명, 시스템 구조에 따른 에러 등등 클라이언트 측으로 안내해서 좋지 않는 에러도 존재할 것이다.

개발을 계속하며 어떠한 에러들을 세분화하여 안내하면 좋을지에 대한 부분도 확인하고 종류를 작성해서 정리하는 것도 좋을 것이라고 생각한다.

'Project > PickLab' 카테고리의 다른 글

이메일 인증 기능 비동기 적용 과정  (0) 2025.07.11
CI/CD 파이프라인 구축기  (0) 2025.05.07
'Project/PickLab' 카테고리의 다른 글
  • 이메일 인증 기능 비동기 적용 과정
  • CI/CD 파이프라인 구축기
dev_Mins
dev_Mins
  • dev_Mins
    천천히 빠르게!
    dev_Mins
  • 전체
    오늘
    어제
    • 분류 전체보기 (40)
      • 42Seoul (2)
      • Back-End (19)
        • Spring (8)
      • Project (14)
        • PickLab (3)
      • 끄적끄적 (3)
      • Algorithm (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    스프링 jwt
    무한스크롤 성능
    AWS
    페이징 성능
    Spring Boot
    무한스크롤 페이징
    42서울
    spring stomp
    db이중화
    spring 트래픽
    이메일 인증 비동기
    스프링
    무한스크롤 페이징 성능
    excpetionhandler
    Spring
    42seoul
    Spring Security
    JWT
    Spring Data JPA
    로드밸런서
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dev_Mins
예외 상황에서 트래킹하기 편하도록 응답 핸들러를 작성해보자
상단으로

티스토리툴바