안녕하세요, 여러분의 든든한 IT 길잡이 데브프리입니다.
이전 포스팅에서 다루었던 '405 에러(메서드 불일치)'를 무사히 해결하고, 드디어 올바른 POST 방식으로 데이터를 보냈습니다. 그런데 이번에는 데이터를 받자마자 서버가 또 다른 붉은 에러를 뱉어냅니다.
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain;charset=UTF-8' not supported (혹은 415 Unsupported Media Type 상태 코드)
"방식(POST)도 맞췄고, 데이터도 잘 적어서 보냈는데 미지원 미디어 타입이라니?"
초보 개발자들이 프론트엔드와 백엔드를 처음 연결할 때 가장 많이 마주치는 통곡의 벽입니다. 오늘은 이 '데이터 규격 불일치 에러(415)'의 원인을 직관적인 비유로 알아보고, 프론트엔드와 백엔드 양쪽에서 단 1분 만에 해결하는 방법을 깔끔하게 정리해 드리겠습니다!
1. 415 Unsupported Media Type 에러, 도대체 넌 누구냐? (DVD 플레이어 비유)
에러 메시지의 Content type '...' not supported라는 말은 "네가 보낸 데이터의 '형태(포맷)'를 우리 서버는 읽을 수 없어!"라는 뜻입니다.

현대의 스프링 부트(Spring Boot) 웹 서버는 데이터를 주고받을 때 주로 JSON (application/json)이라는 아주 깔끔하고 규격화된 DVD 포맷을 사용합니다. 컨트롤러에 @RequestBody를 적어두었다면, 이는 "나는 무조건 JSON 형태의 데이터만 받을 거야!"라는 강력한 선언입니다.
그런데 클라이언트(포스트맨, 브라우저)가 JSON이 아닌 단순 텍스트(text/plain)나 옛날 폼 데이터(x-www-form-urlencoded - 비디오테이프) 형식으로 데이터를 냅다 던진 것입니다.
서버 입장에서는 포장지를 뜯어보니 자신이 전혀 해석할 수 없는 형태의 데이터가 들어있으니, 415 에러를 던지며 수신을 거부하게 됩니다.
2. 이 에러가 발생하는 3가지 흔한 실수와 해결법
서버(DVD 플레이어)가 요구하는 규격과 클라이언트가 보내는 규격이 엇갈리는 3가지 상황을 확인해 봅시다.
실수 ①: 포스트맨(Postman)에서 Body 포맷을 잘못 골랐을 때
백엔드 개발자가 API 테스트를 할 때 가장 흔하게 저지르는 실수입니다. 데이터를 적어 넣긴 했지만, 포스트맨의 Body 탭 설정이 Text나 form-data로 되어 있는 경우입니다.
- ✅ 해결법: 포스트맨의 Body 탭을 클릭 ➡️ raw 선택 ➡️ 우측 끝의 파란색 텍스트(Text)를 클릭하여 JSON으로 변경합니다. 이렇게 설정하면 포스트맨이 알아서 Content-Type: application/json이라는 딱지를 붙여서 서버로 보내줍니다.

실수 ②: 프론트엔드(React, Vue 등)에서 Header 설정을 깜빡했을 때
Axios나 Fetch API를 사용해 데이터를 보낼 때, 자바스크립트 객체를 넘기면서 헤더(Header) 설정을 누락한 경우입니다.
// 🚨 에러 발생: 헤더가 없어서 브라우저가 멋대로 text나 폼 데이터로 보내버림
fetch("http://localhost:8080/api/users", {
method: "POST",
body: JSON.stringify(userData)
});
// ✅ 해결 방법: Content-Type을 명시적으로 적어주어야 함
fetch("http://localhost:8080/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json" // 💡 "이건 JSON이야!" 라고 알려줌
},
body: JSON.stringify(userData)
});
실수 ③: 백엔드 코드에 @RequestBody를 빼먹었을 때 (번외)
클라이언트는 JSON으로 아주 잘 보냈는데, 백엔드 개발자가 실수로 컨트롤러 파라미터 앞에 @RequestBody를 빼먹은 경우가 있습니다.
// 🚨 에러 유발: @RequestBody가 없으면 스프링은 폼 데이터(x-www-form-urlencoded)를 기대함
@PostMapping("/api/users")
public String createUser(UserDto userDto) { ... }
// ✅ 정상 코드: JSON 데이터를 받으려면 반드시 붙여야 함!
@PostMapping("/api/users")
public String createUser(@RequestBody UserDto userDto) { ... }
@RequestBody가 없으면 스프링은 자동으로 옛날 폼 데이터 방식을 기대합니다. 그런데 JSON이 날아오니 서로 말이 안 통하게 되는 것입니다.
3. 요약: 트러블슈팅 체크리스트
HttpMediaTypeNotSupportedException (415 에러)가 떴다면 당황하지 말고 아래 3가지를 점검하세요.
- 포스트맨 설정 확인: Body 탭이 raw ➡️ JSON으로 정확히 설정되어 있는지 확인합니다.
- 프론트엔드 코드 확인: 요청 헤더에 "Content-Type": "application/json"이 잘 들어가 있는지 확인합니다.
- 백엔드 컨트롤러 확인: JSON을 받아야 하는 DTO 객체 앞에 @RequestBody 어노테이션이 빼먹지 않고 잘 붙어 있는지 확인합니다.
마무리
네트워크 통신은 결국 눈치가 없는 기계들끼리의 대화입니다. 내용물(데이터)을 찰떡같이 잘 만들었더라도, "이건 무슨 데이터야"라는 명찰(Content-Type)을 똑바로 달아주지 않으면 상대방 기계는 절대 알아먹지 못합니다.
오늘 배운 415 에러는 코드가 망가진 것이 아니라, 단순히 '포장지 라벨을 잘못 붙인 실수'일 뿐입니다. 에러 메시지 안에 답(Media Type Not Supported)이 있다는 사실을 기억하시고, 프론트엔드의 헤더 설정과 백엔드의 어노테이션을 다시 한번 크로스체크해 보세요!
오늘 준비한 트러블슈팅 가이드가 여러분의 붉은 에러 창을 깨끗하게 지워주었기를 바랍니다. 오류를 고친 경험은 여러분의 가장 큰 자산이 됩니다. 막히는 에러가 있다면 언제든 댓글로 남겨주세요.
지금까지 데브프리였습니다. 감사합니다!
'💻 트러블슈팅 (에러 해결)' 카테고리의 다른 글
| 포스트맨이 멈추지 않아요! JPA 순환 참조(무한 루프) 에러 원인과 DTO 해결법 (거울 방 비유) (0) | 2026.07.21 |
|---|---|
| DB가 응답하지 않아요! Communications link failure 원인과 3분 해결법 (식당 전화 비유) (0) | 2026.07.20 |
| 405 에러? HttpRequestMethodNotSupportedException 원인과 1분 해결법 (당기시오 문을 미는 중!) (0) | 2026.07.19 |
| 이미 사용 중인 포트? Port 8080 was already in use 에러 원인과 초간단 해결법 (0) | 2026.07.18 |
| 기본 생성자가 없다고요? Cannot construct instance of 에러 원인과 1초 해결법 (0) | 2026.07.02 |