안녕하세요, 여러분의 든든한 IT 길잡이 데브프리입니다.
프론트엔드 개발자나 포스트맨(Postman)으로 API 테스트를 진행하다 보면, 서버가 터진 것도 아니고 주소가 틀린 것도 아닌데 '400 Bad Request'라는 찝찝한 상태 코드가 떨어질 때가 있습니다.
스프링 부트(Spring Boot)의 콘솔 창을 열어보면 아래와 같은 붉은 에러 메시지가 찍혀 있죠.
org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'username' for method parameter type String is not present
"분명히 데이터 잘 보냈는데 왜 파라미터가 없다는 거지?"
초보 개발자들이 API를 연동할 때 415 에러(데이터 형식 불일치)만큼이나 자주 겪는 단골 에러입니다.
오늘은 이 '필수 파라미터 누락 에러(400)'의 원인을 관공서의 서류 제출 과정에 비유하여 알아보고, 단 1분 만에 허탈할 정도로 쉽게 해결하는 방법을 정리해 드리겠습니다!
1. Required request parameter is not present, 도대체 왜 뜨는 걸까? (관공서 필수 서류 비유)
이 에러 메시지를 직역하면 "네가 요청할 때 필수로 줘야 하는 파라미터(데이터)가 현재 존재하지 않아!"라는 뜻입니다.

스프링 부트 백엔드 개발자는 컨트롤러(창구 직원)를 만들 때 특정 데이터가 반드시 들어오기를 기대하며 코드를 짭니다. 특히 검색어나 게시글 번호를 받을 때 사용하는 @RequestParam 어노테이션은, 기본적으로 required = true (필수 제출 서류)라는 아주 엄격한 규칙을 깔고 있습니다.
그런데 프론트엔드(시민)가 요청을 보낼 때, 직원이 요구한 이 '필수 서류'를 아예 빼먹고 오거나 서류 봉투에 이름을 잘못 적어서 제출한 것입니다.
서버 입장에서는 "이 데이터가 없으면 난 다음 로직을 처리할 수 없어!"라며 400 Bad Request (잘못된 요청) 에러를 던지며 문전박대하는 것이죠.
2. 이 에러가 발생하는 3가지 흔한 실수와 해결법
창구 직원(스프링 부트)이 서류를 반려하는 가장 대표적인 3가지 상황을 확인해 봅시다.
실수 ①: 파라미터 이름(Key)에 오타가 났을 때 (가장 흔함!)
프론트엔드와 백엔드가 서로 다른 이름을 부르고 있을 때 발생합니다. 백엔드는 소문자 username을 기다리는데, 프론트엔드(또는 포스트맨)는 대문자가 섞인 userName으로 데이터를 보낸 경우입니다.
- 🚨 프론트엔드 요청: GET /api/users?userName=devfree
- 🚨 백엔드 코드:
Java @GetMapping("/api/users") public String getUser(@RequestParam String username) { ... } - ✅ 해결법: 양쪽의 변수명을 대소문자까지 완벽하게 하나로 통일하세요. 컴퓨터는 username과 userName을 아예 다른 서류로 인식합니다.

실수 ②: @RequestParam 과 @PathVariable을 헷갈렸을 때
데이터를 주소에 담아 보내는 방식은 2가지가 있습니다. ?id=1 처럼 쿼리 스트링으로 보내는 방식과 /users/1 처럼 경로 자체에 담는 방식입니다. 이 두 가지를 혼용하면 에러가 터집니다.
- 🚨 클라이언트 요청: /api/users/1 (경로에 담아서 보냄)
- 🚨 백엔드 코드:
Java // @RequestParam은 무조건 '?id=1' 형태를 기대합니다! @GetMapping("/api/users/{id}") public String getUser(@RequestParam Long id) { ... } - ✅ 해결법: 클라이언트가 /users/1 형태로 보낸다면, 백엔드는 @RequestParam이 아니라 @PathVariable을 사용해야 합니다.
Java @GetMapping("/api/users/{id}") public String getUser(@PathVariable Long id) { ... }
실수 ③: 정말로 파라미터를 아예 안 보냈을 때
포스트맨에서 Params 탭의 체크박스를 해제한 채로 보내거나, 프론트엔드에서 실수로 ?username= 형태로 값을 텅 비워서 보낸 것이 아니라 아예 키(Key) 자체를 누락한 경우입니다.
3. 🚨 실무 꿀팁: 서류를 안 가져와도 유연하게 넘어가기 (required = false)
실무에서는 "검색어를 입력하면 검색 결과를 주고, 검색어를 입력 안 하면 전체 목록을 줘라" 같은 요구사항이 아주 많습니다.
이럴 때는 창구 직원(스프링 부트)의 성격을 조금 부드럽게 만들어주면 됩니다.
Java
// 💡 required = false 를 주면 필수가 아닌 '선택 서류'로 바뀝니다!
@GetMapping("/api/users")
public String getUser(@RequestParam(required = false) String username) {
if (username == null) {
return "전체 유저 목록 조회";
}
return username + " 유저 조회";
}
혹은 아예 기본값(defaultValue)을 지정해 줄 수도 있습니다. 서류를 안 가져오면 직원이 알아서 기본 서류로 채워 넣는 방식입니다.
Java
// 💡 값을 안 보내면 자동으로 page 번호를 "1"로 세팅합니다.
@GetMapping("/api/posts")
public String getPosts(@RequestParam(defaultValue = "1") int page) {
return page + "번 페이지 조회";
}
💡 [데브프리의 실무 꿀팁: required = false를 썼는데 500 에러가 터진다고요?]
필수가 아닌 파라미터를 받을 때 초보자들이 가장 많이 하는 치명적인 실수가 있습니다. 바로 아래와 같이 코드를 짜는 것입니다.
@RequestParam(required = false) int age 파라미터가 안 들어오면 스프링은 이 변수에 null을 넣으려고 시도합니다. 하지만 자바에서 int, long, boolean 같은 원시 타입(Primitive Type)에는 절대로 null이 들어갈 수 없습니다! 그래서 400 에러 대신 끔찍한 500 서버 에러(500 Internal Server Error)가 터지게 됩니다.
이를 방지하려면 int 대신 객체 타입인 Integer를 사용하거나, 위 예시처럼 반드시 defaultValue를 함께 지정해주어야 한다는 점을 꼭 기억하세요!
4. 요약: 트러블슈팅 체크리스트
MissingServletRequestParameterException (400 에러)가 떴다면 당황하지 말고 아래 3가지를 점검하세요.
- 파라미터명 (Key) 오타 확인: 포스트맨(프론트엔드)에서 보낸 Key 값과 백엔드 컨트롤러 파라미터의 스펠링, 대소문자가 100% 일치하는지 확인합니다.
- 요청 방식 매칭 확인: 주소가 /users/1 형태인지, /users?id=1 형태인지 확인하고 그에 맞는 어노테이션(@PathVariable vs @RequestParam)을 사용했는지 점검합니다.
- 필수 여부 완화 검토: 값이 안 들어올 수도 있는 파라미터라면 @RequestParam(required = false) 처리를 해줍니다.
마무리
400번대 에러(Bad Request)는 서버(백엔드)가 잘못한 것이 아니라 클라이언트(프론트엔드)가 잘못된 요청을 보냈을 때 발생하는 에러입니다.
하지만 백엔드 개발자라면 "프론트엔드님, 데이터 안 보내셨는데요?"라고 탓하기 전에, 내 API 명세서에 파라미터 이름이 정확하게 적혀 있는지, 필수 여부가 제대로 설계되어 있는지 먼저 점검하는 배려가 필요합니다.
오늘 배운 트러블슈팅 가이드를 통해 프론트엔드와의 소통 오류를 1분 만에 쿨하게 해결하시길 바랍니다! 막히는 에러가 있다면 언제든 댓글로 남겨주세요.
지금까지 데브프리였습니다. 감사합니다!
👉 프론트엔드랑 연결할 때 에러가 난다면? 1초 만에 끝내는 스프링 부트 CORS 설정 코드 생성기 [링크]
'💻 트러블슈팅 (에러 해결)' 카테고리의 다른 글
| 화면이 안 떠요! Error resolving template 원인과 1분 해결법 (유령 접시 비유) (0) | 2026.07.26 |
|---|---|
| DB가 데이터를 뱉어내요! DataIntegrityViolationException 원인과 해결법 (영화관 지정좌석 비유) (0) | 2026.07.25 |
| 파일 업로드가 튕겨요! MaxUploadSizeExceededException 원인과 1분 해결법 (공항 수하물 비유) (0) | 2026.07.23 |
| 415 에러? HttpMediaTypeNotSupportedException 원인과 1분 해결법 (DVD와 비디오테이프 비유) (0) | 2026.07.22 |
| 포스트맨이 멈추지 않아요! JPA 순환 참조(무한 루프) 에러 원인과 DTO 해결법 (거울 방 비유) (0) | 2026.07.21 |