계층별 검증 표준은 검증 실패 응답을 한 자리에서 만들기로 했고, 근거는 이랬다.

@RequestBody @Valid 실패는 MethodArgumentNotValidException 으로 올라와
@RestControllerAdvice 의 한 핸들러에서 필드별 오류를 만든다.

「한 핸들러」가 errors 응답 형식을 보장하는 자리다. 검증 실패가 정말 한 타입으로 모이는지 쟀다.


주장 — 요청 값 검증 실패는 한 예외로 올라온다

요청 값 검증 실패는 MethodArgumentNotValidException 하나로 올라와
Advice 의 한 핸들러에서 받는다.

근거 종류는 1(기술 사실)이다.


반증 조건 — 잡힌 핸들러가 갈리면 반증이다

  • refuted — 두 환경 중 하나라도 세 경우가 한 핸들러로 모이지 않는다
  • supported — 두 환경 모두 세 경우가 MethodArgumentNotValidException 핸들러 하나로 모인다
  • inconclusive — 서버가 뜨지 않거나 어느 경우가 검증 실패 없이 200으로 끝난다

설계 — 예외 타입마다 핸들러를 두고 어느 쪽이 잡는지 표식을 찍는다

Advice에 MethodArgumentNotValidException · HandlerMethodValidationException · BindException · ConstraintViolationException · Exception 핸들러를 각각 두고, 응답 본문에 어느 핸들러가 잡았는지 표식을 넣었다.

(가) 본문 객체      POST /probe        @RequestBody @Valid, @NotBlank 위반
(나) 파라미터 제약   GET  /probe?size=0  컨트롤러 @Validated + 파라미터 @Min(1)
(다) 폼·쿼리 객체   GET  /probe/search  @ModelAttribute @Valid, @NotBlank 위반

임베디드 톰캣을 실제로 띄웠고(server.port=0) 환경은 Spring Boot 3.5.16과 4.1.1이다. 네 예외 타입이 두 버전에 다 존재하는지도 함께 기록했다 — 전부 있었다.


결과 — 파라미터 제약만 다른 핸들러로 갔다

PROBE boot=3.5.16 (가) 400 manv MethodArgumentNotValidException
                  (나) 400 cv   ConstraintViolationException
                  (다) 400 manv MethodArgumentNotValidException
PROBE boot=4.1.1  (같음)

판정은 refuted다. 상태 코드는 셋 다 400으로 같지만 잡은 핸들러가 갈린다.


예상과 달랐던 점 — 갈림은 Boot 버전이 아니라 검증이 걸리는 자리였다

두 Boot 버전이 완전히 같았다. 갈린 기준은 값이 어디에 실려 오는가다.

경우 예외 왜 갈리나
본문 객체 MethodArgumentNotValidException 객체를 바인딩한 뒤 검증한다
폼·쿼리 객체 MethodArgumentNotValidException 같은 경로다
파라미터 제약 ConstraintViolationException 메서드 파라미터 검증이라 경로가 다르다

폼 객체가 BindException이 아니라 MethodArgumentNotValidException이었다는 것도 예상 밖이다. 두 타입을 다 두고 쟀기 때문에 구분됐다.

범위를 밝힌다. claim.md가 컨트롤러에 @Validated를 달도록 지정했고 그 설정이 메서드 검증 경로를 태운다. @Validated 없이 파라미터에 제약만 단 변형은 돌리지 않았으므로 그쪽 결과는 모른다.


이 결과가 표준에 남기는 것 — 핸들러가 하나로 부족하다

규칙(검증 실패는 errors로 필드별로 내려준다)은 흔들리지 않는다. 깨진 것은 한 핸들러로 다 받는다는 전제다. 지금 글대로 MethodArgumentNotValidException 핸들러만 두면 파라미터 제약 위반은 그 핸들러를 안 타고, errors 형식이 아닌 응답이 나간다.

규칙은 바로 고치지 않는다. 표준 글에 > 반증: 줄을 달고 kind: 모순 이슈에서 핸들러를 몇 개 둘지, 세 경로의 응답을 같은 형식으로 맞출지를 정한다.


정리

  • 본문 객체와 폼 객체는 MethodArgumentNotValidException으로 모인다
  • 파라미터 제약은 ConstraintViolationException으로 갈린다. 두 Boot 버전이 같았다
  • 상태 코드는 셋 다 400이다. 갈리는 것은 예외 타입과 응답 형식이다
  • 재지 않은 것@Validated 없이 제약만 달았을 때

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기