본문 바로가기
IT

[Spring Boot 3] @Valid 예외 처리 완전 정복: MethodArgumentNotValidException vs BindException

by 굿센스굿 2025. 5. 9.
반응형

 

🔍 들어가며

Spring Boot 3에서는 @Valid 또는 @Validated 어노테이션을 통해 컨트롤러에 전달되는 요청 객체의 유효성을 자동으로 검사할 수 있습니다. 하지만 검증 실패 시 발생하는 예외를 그대로 방치하면, 지나치게 긴 에러 메시지 또는 구조화되지 않은 응답으로 인해 클라이언트에서 처리하기 어려워지는 문제가 발생합니다.

이 글에서는 Spring Boot 3에서 @Valid를 사용할 때 발생하는 대표적인 예외 MethodArgumentNotValidException, BindException에 대해 알아보고, 이를 구조화된 응답으로 변환하여 깔끔하고 직관적인 예외 처리를 구현하는 방법을 단계별로 설명하겠습니다.


✅ 1. 유효성 검증 실패 시 발생하는 예외 알아보기

1-1. @NotBlank 유효성 검증 실패 예시

예를 들어 다음과 같은 요청 객체가 있다고 가정해보겠습니다:

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ValidObject {
    @NotBlank(message = "문자열은 비어 있을 수 없습니다.")
    private String string;
}

이 객체를 @Valid와 함께 사용하여 컨트롤러에서 검증하면, 다음과 같은 로그가 출력됩니다:

Resolved [org.springframework.web.bind.MethodArgumentNotValidException:
Validation failed for argument [0] in ...
Field error in object 'validObject' on field 'string': rejected value [ ];
...
default message [문자열은 비어 있을 수 없습니다.]]

로그를 보면, 유효성 검사를 통과하지 못했을 때 MethodArgumentNotValidException이 발생하는 것을 확인할 수 있습니다.


⚙️ 2. MethodArgumentNotValidException을 이용한 예외 처리

가장 기본적인 방식은 @RestControllerAdvice에서 해당 예외를 캐치하여 응답을 조작하는 것입니다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Response> handleValidationException(MethodArgumentNotValidException e) {
        return ResponseEntity
                .badRequest()
                .body(new Response("E", e.getMessage()));
    }

    @Getter
    @AllArgsConstructor
    public static class Response {
        private String code;
        private String message;
    }
}

✅ 장점

  • 빠르게 구현 가능
  • 모든 컨트롤러에서 재사용 가능

❌ 단점

  • e.getMessage()는 지나치게 상세하고 복잡한 문자열을 반환해 가독성이 떨어짐
  • 클라이언트에서 JSON 파싱이 어려움

🔁 3. BindingResult와 BindException 활용하기

보다 구조화된 예외 응답을 원한다면, BindingResult를 활용해 직접 유효성 검사를 하고, 실패 시 BindException을 던질 수 있습니다.

@PostMapping("/valid/exception")
public ValidObject bindException(@Valid @RequestBody ValidObject object, BindingResult result) throws BindException {
    if (result.hasErrors()) {
        throw new BindException(result);
    }
    return object;
}

이 경우, 예외는 MethodArgumentNotValidException이 아닌 BindException으로 발생합니다.


🛠️ 4. BindException에 대한 사용자 정의 예외 처리

이번에는 BindException을 캐치하여 필요한 정보만 추려 응답을 구성해보겠습니다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BindException.class)
    public ResponseEntity<Response> handleBindException(BindException e) {
        List<FieldError> fieldErrors = e.getFieldErrors();
        List<Response.ErrorField> errors = new ArrayList<>();
        for (FieldError fieldError : fieldErrors) {
            errors.add(new Response.ErrorField(
                fieldError.getRejectedValue(),
                fieldError.getDefaultMessage()));
        }
        return ResponseEntity
                .badRequest()
                .body(new Response("BE", "유효성 검증 실패", errors));
    }

    @Getter
    @AllArgsConstructor
    public static class Response {
        private String code;
        private String message;
        private List<ErrorField> errors;

        @Getter
        @AllArgsConstructor
        public static class ErrorField {
            private Object value;
            private String message;
        }
    }
}

🧼 예외 응답 예시

{
  "code": "BE",
  "message": "유효성 검증 실패",
  "errors": [
    {
      "value": "",
      "message": "문자열은 비어 있을 수 없습니다."
    },
    {
      "value": null,
      "message": "숫자는 필수 항목입니다."
    }
  ]
}

📌 5. 두 방식 비교 요약

구분 MethodArgumentNotValidException BindException

발생 시점 Spring 내부에서 자동 처리 컨트롤러에서 수동 처리
유연성 낮음 높음
응답 커스터마이징 어려움 쉬움
사용 용도 간단한 API 커스터마이징된 응답 필요 시

💡 실전 예시로 보는 사용 시나리오

예시 1: 클라이언트에서 어떤 필드가 잘못되었는지 알려줘야 할 때

BindException 방식이 유리합니다.
사용자가 어떤 값을 잘못 입력했는지 rejectedValue와 함께 메시지를 주면 UX 향상에 도움이 됩니다.


예시 2: 단순히 유효성 통과 여부만 빠르게 확인하고 싶은 경우

MethodArgumentNotValidException 방식으로도 충분합니다.


예시 3: 프론트엔드에서 필드 단위 에러 표시가 필요한 경우

BindException + 커스터마이징된 ErrorField 객체 사용이 정답입니다.


✨ 마무리하며

Spring Boot 3에서 @Valid를 사용할 때는 검증 실패에 따른 예외 처리를 반드시 설계해야 합니다. 단순한 메시지 반환에 그치지 않고, 클라이언트에서 실제로 활용할 수 있는 구조화된 예외 응답을 설계한다면 훨씬 더 품질 높은 API를 제공할 수 있습니다.

특히 BindException을 활용한 방식은 실무에서 프론트와의 연동 시 유용하게 사용될 수 있으므로 반드시 익혀두는 것을 추천드립니다.


🔖 관련 태그

#SpringBoot3 #Valid #예외처리 #BindException #MethodArgumentNotValidException #Validation #RestAPI #JavaSpring #API응답구조화 #SpringValidation

 

반응형