배경
Controller에서 http 메서드를 통해 데이터를 받을 때 위와 같은 어노테이션을 사용해서 전달받는다.
@GetMapping("/user")
public ApiResponse<List<String>> getUserName(
@RequestBody GetUserNameRequest request
) {
List<String> result = userFacade.getUserNames( request );
return ApiResult.of( result );
}
----------------------------------------------------------------------
@PostMapping("/user")
public ApiResponse<List<String>> createUser(
@ModelAttribute CreateUserRequest request
) {
List<String> result = userFacade.createUser( request );
return ApiResult.of( result );
}
이처럼 Controller에서 http 메서드를 받는데 위 두 코드를 보면 받아오는 DTO 객체 앞에 어노테이션이 다르다.
처음에는 항상 @ModelAttribute만 사용해왔는데 어떤 경우에는 @RequestBody를 해야 정상적으로 데이터를 받는 경우가 있었다.
분명이 두 어노테이션이 다르겠지만 구체적으로 어떤 차이가 있는지 알아보고자 했다.
@ModelAttribute 어노테이션과 @RequestBody 어노테이션
두 어노테이션 모두 http 프로토콜을 통해 데이터를 받기 위해 사용하는 어노테이션이다.
서로 사용해야 할 상황이 있으며, 잘못 사용할 경우 데이터가 제대로 매팽되지 않거나 아예 데이터를 받아오지 못하는 에러가 뜨고는 한다.
( 대체적으로 400 Bad Request, 또는 415 에러가 났던 것 같다. )
API를 만들 때 반드시 필요한 어노테이션들이며, 각각 어떤 상황에서 사용해야 하는지 알아보았다.
@ModelAttribute = Form 데이터를 받을 때 사용
@ModelAttribute 어노테이션은 주로 form data를 받아야 할 때 사용한다.
이 어노테이션을 통해 데이터를 받으려면 헤더에 Content-Type이 multipart/form-data 또는 application/x-www-form-urlencoded 등과 같은 형태로 넘어와야 한다.
그래서 FE에서 데이터가 넘어올 때 개별 파라미터를 객체의 각 필드에 매핑하는 방식으로 바인딩이 이루어진다.
@RequestBody = 본문을 통째로 받을 때 사용
@RequestBody 어노테이션은 주로 JSON이나 XML 형태로 보낸 내용을 받을 때 사용한다.
이 어노테이션을 통해 데이터를 받으려면 헤더의 Content-Type이 application/json이나 application/xml과 같은 형태로 넘어와야 한다.
요청할 때 JSON이나 XML의 형태로 요청되기 때문에 이 요청 본문을 직접 그 객체로 변환하게 된다.
그럼 두 어노테이션 사용 상황 구분을 어떻게 해야 할까?
가장 정확한 것은 FE 개발자와 이야기를 통해 Content-Type의 형태를 미리 정해두고 통일하는 것이다.
하지만 항상 그럴 수 없으니 일반적인 경우에 주로 어떻게 사용하는지 알아보았다.
일반적으로 FE에서 API를 사용할 때 데이터를 JSON의 형태로 많이 넘긴다.
JSON 객체에 넘기고자 하는 데이터를 담아 넘기기 때문에 이 데이터를 받을 수 있는 @RequestBody가 일반적으로 사용된다.
하지만 JSON 객체에 담을 수 없는 객체를 넘기거나 FE에서 form 태그를 통해 데이터를 넘기는 경우가 있을 것이다.
( 주로 파일을 포함하고 있거나 사용자들에게 input으로 입력 받은 정보들 등을 넘길 때 사용한다. )
이런 경우에는 데이터를 받기 위해 @ModelAttribute를 사용해야 한다.
즉, FE에서 넘어오는 데이터를 적절히 판단해 각 상황에 알맞는 어노테이션을 사용해주어야 한다.
+ 각 상황에 맞지 않은 어노테이션을 쓰면 어떤 일이 벌어질까?
그럼 form data를 @RequestBody로 받으려고 하거나 JSON 객체를 @ModelAttribute로 받으려고 한다면 어떤 일이 생길까?
Case 01) JSON 파일을 받아야 하는데 @ModelAttribute를 사용했다.
이 경우, Spring은 JSON 객체를 form-data 형태로 바인딩을 시도하게 된다.
그러나 당연하게도 JSON을 form 파라미터로 파싱할 수 없기 때문에 에러가 나게 된다.
이 경우, 400 Bad Request 에러가 뜨게 된다.
( 올바르지 못한 형태로 요청이 들어왔기 때문에 HTTP 에러 코드 400 에러가 뜬다. )
Case 02) Form 데이터를 받아야 하는데 @RequestBody를 사용했다.
이 경우, Content-Type에 따라 다른 에러 코드가 나온다.
Form Data는 multipart/form-data 라는 타입과 application/x-www-form-urlencoded라는 타입으로 받을 수 있다.
우선 multipart/form-data 형태로 넘어온 form 데이터였다면 415 Unsupported Media Type이라는 에러가 나온다.
아예 다른 타입이기 때문에 잘못된 요청도 아닌 미지원 미디어 타입이라는 에러를 반환하게 되는 것이다.
반면에 application/x-www-form-urlencoded라는 헤더 타입으로 넘어온 form 데이터였다면 같은 application 형태이기 때문에 지원하는 미디어 타입이기는 하나 form 데이터를 JSON으로 파싱할 수 없으므로 400 Bad Request 에러가 나오게 된다.
결론
FE에서 HTTP 메서드를 통해 데이터를 전달하는 과정에서 400에러나 415 에러가 난다면 자신이 알맞은 어노테이션을 사용해서 데이터를 받고 있는 것인지 확인해볼 필요가 있다.
협업을 하는 과정에서 생각보다 이 부분에서 에러가 나오는 경우가 많다.
API 테스트를 위해 주로 Swagger를 연동해 사용하는데 Swagger에서는 컨트롤러의 코드에 맞게 타입을 지정해줘 테스트 할 때는 문제 없이 작동하지만, 실제 FE 개발자 분들이 API를 연동할 때 다른 형태로 전달해 API가 작동하지 않는다고 말씀해주시는 경우를 가끔 보았다.
분명히 둘 다 맞게 작성했음에도 400에러가 나거나 415 에러가 난다면 어노테이션을 한번 확인해보는 것도 좋은 트러블 슈팅 전략이 될 것 같다.
'Back-End > Spring Boot' 카테고리의 다른 글
| [JPA] Spring Boot JPA를 통한 Entity 조회 (0) | 2025.10.01 |
|---|---|
| [Spring Boot] Logging ( feat. Slf4j ) (0) | 2025.09.29 |
| [Spring Boot] Exception Handler를 통한 예외 일괄 관리 (1) | 2025.09.24 |
| [Spring Boot][Trouble Shooting] 서비스 자동 재시작 기능 (0) | 2025.09.05 |
| [Spring Boot] Spring Boot의 스케쥴링 기능 (0) | 2025.06.30 |