이번 강의에서는
REST API 환경에서 @Valid와 BindingResult를 어떻게 바라봐야 하는지에 대해 다뤘다.
솔직히 말하면
강의 내용을 처음 들었을 때는 나도 꽤 헷갈렸다.
“프론트에서 이미 다 검증하는데
굳이 백엔드에서 또 검증을 해야 하나?”
이 글에서는
왜 @Valid는 쓰지만 BindingResult는 거의 쓰지 않는지,
그리고 JSON 요청이 record DTO로 바인딩되는 정확한 흐름까지
초보자 관점에서 정리해본다.
Spring MVC(타임리프, JSP) 기반에서는 이런 코드가 흔하다.
@PostMapping("/write")
public String write(
@Valid PostForm form,
BindingResult bindingResult
) {
if (bindingResult.hasErrors()) {
return "writeForm";
}
...
}
👉 뷰(View)가 존재하는 서버 사이드 렌더링 구조에서는
에러가 나도 다시 화면으로 돌아가야 하기 때문에
BindingResult가 필수에 가깝다.
REST API의 특징은 이렇다.
그래서 REST API에서 굳이:
👉 BindingResult 패턴이 잘 맞지 않는다.
강의에서 나온 핵심 문장은 이거였다.
REST API에서는
@Valid에서 실패하면 그냥 “터지게 둔다”
즉,
@PostMapping
@Transactional
public RsData<PostDto> write(
@Valid @RequestBody PostWriteForm form
) {
...
}
여기서 유효성 검증이 실패하면?
MethodArgumentNotValidException 발생👉 의도된 실패 흐름이다.
정상적인 사용자라면
서버로 잘못된 데이터가 올 일이 거의 없다.
👉 프론트를 우회하는 요청을 막기 위한 최후의 방어선
REST API에서 유효성 실패는
복구해서 이어갈 일이 아니라, 바로 종료할 사건이다.
그래서 BindingResult를 쓰지 않는다.
BindingResult를 쓰면:
if (bindingResult.hasErrors()) {
...
}
👉 REST API에서는
이 패턴 자체가 오히려 설계를 복잡하게 만든다.
“프론트에서 다 검증하는데 왜 @Valid를 쓰지?”
👉 이 질문에 대한 답은 명확하다.
👉 서버는 절대 클라이언트를 신뢰하면 안 된다.
그래서:
이렇게 역할이 나뉜다.
클라이언트에서 이런 JSON을 보낸다고 가정하자.
{
"title": "제목",
"content": "내용"
}
그리고 서버에서는 이렇게 받는다.
record PostWriteForm(
@NotBlank
@Size(min = 2, max = 100)
String title,
@NotBlank
@Size(min = 2, max = 5000)
String content
) {}
그 이유는 Jackson(ObjectMapper) 때문이다.
Jackson은:
을 기준으로 객체를 만든다.
즉,
"title" → String title
"content" → String content
👉 이름이 정확히 같아야 매핑된다.
만약 JSON이 이렇게 온다면?
{
"subject": "제목",
"body": "내용"
}
하지만 record는:
record PostWriteForm(String title, String content) {}
👉 결과는?
👉 컨트롤러 진입 자체가 실패한다.
이게 바로
“프론트와 백엔드가 데이터 계약을 맞춰야 하는 이유”다.
REST API에서의 유효성 검증은
“사용자를 도와주는 기능”이 아니라
“서버를 지키는 안전장치”다.