안녕하세요, 여러분의 든든한 IT 길잡이 데브프리입니다.
프론트엔드와 백엔드를 연결하여 로그인이나 게시글 작성(POST) API를 테스트할 때, 프론트엔드 개발자가 "백엔드님, 데이터 보냈는데 400 Bad Request 에러가 떠요!"라고 다급하게 부르는 경우가 있습니다.
백엔드 콘솔창을 열어보면 이런 길고 무시무시한 에러 메시지가 붉게 빛나고 있죠. org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing...
또는 JSON parse error: Unexpected character...
분명히 데이터를 보냈다는데, 백엔드는 왜 읽을 수 없다고 파업을 선언하는 걸까요?
오늘은 프론트와 통신할 때 가장 자주 마주치는 'HttpMessageNotReadableException'의 원인을 직관적으로 짚어보고, 이 문제를 단숨에 해결하는 2가지 포인트까지 확실하게 정리해 드리겠습니다!
1. HttpMessageNotReadable 에러, 도대체 넌 누구냐? (외계어 편지 비유)
HttpMessageNotReadableException은 단어 그대로 "네가 보낸 HTTP 메시지(데이터)를 내가 도저히 읽을(Readable) 수가 없다!"라며 백엔드 서버가 뱉어내는 에러입니다.

일상생활에 비유해 볼까요?
프론트엔드(클라이언트)가 백엔드 서버에게 "게시글 저장해 줘!"라며 우편물(HTTP 요청)을 보냈습니다. 백엔드는 우편물을 뜯어보고 자바(Java) 객체로 예쁘게 변환하려고 했는데, 봉투 안에 편지가 아예 없거나(Body 누락), 글씨가 다 번져서 도무지 읽을 수 없는 외계어(JSON 문법 오류)로 적혀 있던 것입니다.
스프링 부트는 @RequestBody 어노테이션을 통해 들어온 JSON 데이터를 자바 객체로 변환(역직렬화)하는데, 이 데이터의 형태가 올바르지 않으면 즉시 읽기를 포기하고 400 에러를 던져버립니다.
2. 스프링 부트에서 이 에러가 발생하는 2가지 주요 원인
이 에러의 원인은 백엔드 코드의 논리적 결함이라기보다는, '프론트엔드에서 넘어온 데이터의 형태'가 잘못되었을 확률이 99%입니다. 가장 흔한 실수 2가지를 확인해 봅시다.

원인 ①: Body에 데이터를 아예 안 보냈을 때 (Required request body is missing)
POST나 PUT 요청을 보낼 때 데이터를 Body에 담아서 보내야 하는데, 실수로 주소(URL)만 호출했거나 데이터를 담는 것을 깜빡한 경우입니다.
- [해결 방법]: 포스트맨(Postman)으로 테스트 중이라면 Body 탭 -> raw -> JSON이 제대로 선택되어 있고 내부에 데이터가 적혀 있는지 확인하세요. 프론트엔드 코드(Axios, Fetch 등)에서 data 객체를 빼먹지 않았는지 체크해야 합니다.
원인 ②: JSON 문법 오류 및 타입 불일치 (JSON parse error)
데이터는 보냈지만, JSON 규칙을 지키지 않아서 스프링 부트가 해석하다가 기절해 버린 경우입니다.
[잘못된 JSON 예시 - 파싱 에러 발생]
{
"title": "게시글 제목입니다", // 🚨 마지막 데이터가 아닌데 콤마(,)가 빠지면 에러!
"content": "내용입니다"
"age": "스물다섯" // 🚨 백엔드(자바)에서는 int 형으로 선언했는데, 문자열("스물다섯")을 보낸 경우 에러!
}
- [해결 방법]: 1. JSON 데이터 안에 콤마(,), 큰따옴표(""), 중괄호({})가 제대로 닫혀 있는지 확인합니다. (마지막 줄에는 콤마가 없어야 합니다.) 2. 프론트엔드에서 넘기는 값과 백엔드 DTO 클래스의 데이터 타입(String, int, boolean 등)이 정확히 일치하는지 대조해 보아야 합니다.
3. 요약: 트러블슈팅 체크리스트
400 Bad Request와 함께 HttpMessageNotReadableException이 발생했다면, 당황하지 말고 아래 3가지를 체크하세요.
- 에러 로그 상세 확인: 콘솔 창의 에러 메시지 뒤쪽을 보면 Unexpected character ('"' (code 34)) 혹은 Cannot deserialize value of type 처럼 어떤 문자가 틀렸는지, 어떤 타입이 안 맞는지 아주 상세하게 알려줍니다.
- Network(네트워크) 탭 확인: 프론트엔드(크롬 개발자 도구 - Network 탭)의 Payload를 열어서, 백엔드로 진짜 데이터가 날아갔는지, 텅 빈 객체({})가 날아가지 않았는지 확인합니다.
- DTO 클래스 변수명 대조: 프론트에서 보내는 JSON의 Key 값("title")과 백엔드 DTO 클래스의 변수명(private String title;)이 철자 하나 안 틀리고 완벽하게 동일한지 확인합니다.
마무리
프론트엔드와 백엔드가 처음으로 데이터를 주고받을 때 가장 자주 마주치는 이 에러는, 두 서버가 서로 다른 언어(JSON vs Java)로 대화하다가 발생하는 아주 자연스러운 '통역 오류'일 뿐입니다.
내가 짠 백엔드 코드가 잘못된 것이 아니라, 넘어온 편지(데이터)의 글씨가 조금 삐뚤어졌을 뿐이니 로그를 천천히 읽어보고 프론트엔드 개발자와 포맷을 꼼꼼히 맞춰보세요. 금방 훌륭한 데이터를 받아볼 수 있을 것입니다.
오늘 준비한 트러블슈팅 가이드가 여러분의 붉은 에러 창을 깨끗하게 지워주었기를 바랍니다. 오류를 고친 경험은 여러분의 가장 큰 자산이 됩니다. 막히는 에러가 있다면 언제든 댓글로 남겨주세요.
지금까지 데브프리였습니다. 감사합니다!
'💻 트러블슈팅 (에러 해결)' 카테고리의 다른 글
| 영원한 JPA의 숙제! LazyInitializationException (no Session) 원인과 해결법 완벽 정리 (0) | 2026.06.30 |
|---|---|
| 데이터 형식 불량! MethodArgumentNotValidException 원인과 해결법 완벽 정리 (0) | 2026.06.29 |
| 분명 데이터는 있는데 500 에러? No serializer found for class 원인과 해결법 (0) | 2026.06.27 |
| 공포의 하얀 화면! 스프링 부트 Whitelabel Error Page (404) 원인과 해결법 완벽 정리 (0) | 2026.06.26 |
| 자바 개발자의 영원한 숙적! NullPointerException (NPE) 원인과 스프링 부트 해결법 완벽 정리 (0) | 2026.06.25 |