Spring MVC에서 @ModelAttribute는 @PostMapping에서도 자동으로 동작합니다. Form 데이터와 JSON 요청의 차이, @RequestBody와의 사용 기준, 자동 바인딩 원리까지 예제와 함께 자세히 정리했습니다.

Spring을 처음 배우다 보면 @ModelAttribute는 GET 요청에서만 사용하는 것처럼 느껴질 수 있습니다. 특히 @PostMapping에서 DTO가 자동으로 채워지는지 궁금해하는 경우가 많습니다.
결론부터 말하면 @ModelAttribute는 GET뿐만 아니라 POST 요청에서도 자동으로 데이터를 바인딩합니다. 다만 요청 방식과 Content-Type에 따라 사용할 어노테이션이 달라집니다. 이 차이만 이해하면 @ModelAttribute와 @RequestBody를 언제 사용해야 하는지 쉽게 구분할 수 있습니다.
@ModelAttribute는 POST 요청에서도 자동으로 동작한다
예를 들어 로그인 정보를 전달하는 DTO가 있다고 가정해 보겠습니다.
public class LoginDto {
private String id;
private String password;
// getter, setter
}
컨트롤러는 다음과 같이 작성할 수 있습니다.
@PostMapping("/login")
public String login(@ModelAttribute LoginDto dto) {
System.out.println(dto.getId());
System.out.println(dto.getPassword());
return "ok";
}
클라이언트가 다음과 같이 Form 데이터를 전송하면
POST /login
Content-Type: application/x-www-form-urlencoded
id=hong&password=1234
Spring은 내부적으로 다음과 같이 자동으로 값을 넣어줍니다.
dto.setId("hong");
dto.setPassword("1234");
즉, 개발자가 직접 setter를 호출하지 않아도 요청 파라미터 이름과 DTO의 필드명이 일치하면 자동으로 매핑됩니다.
@ModelAttribute는 생략해도 된다
복합 객체(DTO)를 매개변수로 사용하는 경우에는 @ModelAttribute를 생략해도 동일하게 동작합니다.
@PostMapping("/login")
public String login(LoginDto dto) {
return "ok";
}
Spring MVC는 기본적으로 복합 객체를 @ModelAttribute 방식으로 처리하기 때문입니다.
반면 String, Long, int 같은 단순 타입은 @RequestParam 방식으로 처리됩니다.
JSON 요청은 @ModelAttribute가 아니다
많은 개발자가 가장 많이 헷갈리는 부분입니다.
다음처럼 JSON을 전송하는 경우에는 @ModelAttribute가 동작하지 않습니다.
POST /login
Content-Type: application/json
{
"id":"hong",
"password":"1234"
}
이 경우에는 반드시 @RequestBody를 사용해야 합니다.
@PostMapping("/login")
public String login(@RequestBody LoginDto dto) {
return "ok";
}
@RequestBody는 HTTP Body에 있는 JSON 데이터를 읽어 Jackson과 같은 HttpMessageConverter를 이용해 DTO로 변환합니다.
반면 @ModelAttribute는 Body의 JSON을 읽지 않고 요청 파라미터(Form 데이터)를 기준으로 객체를 생성합니다.
Content-Type에 따라 사용하는 어노테이션
Content-Type@ModelAttribute@RequestBody
| application/x-www-form-urlencoded | 사용 | 사용하지 않음 |
| multipart/form-data | 사용 | 사용하지 않음 |
| application/json | 사용하지 않음 | 사용 |
실무에서는 이 표 하나만 기억해도 대부분의 상황을 구분할 수 있습니다.
fetch()로 요청할 때 차이
JSON 전송
fetch("/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
id: "hong",
password: "1234"
})
});
컨트롤러
@PostMapping("/login")
public String login(@RequestBody LoginDto dto) {
return "ok";
}
FormData 전송
const formData = new FormData();
formData.append("id", "hong");
formData.append("password", "1234");
fetch("/login", {
method: "POST",
body: formData
});
컨트롤러
@PostMapping("/login")
public String login(@ModelAttribute LoginDto dto) {
return "ok";
}
URLSearchParams 전송
const params = new URLSearchParams();
params.append("id", "hong");
params.append("password", "1234");
fetch("/login", {
method: "POST",
body: params
});
이 역시 application/x-www-form-urlencoded 형식으로 전송되므로 @ModelAttribute가 자동으로 DTO를 채워줍니다.
실무에서는 어떻게 사용할까?
실무에서는 다음 기준으로 사용하는 경우가 대부분입니다.
- HTML Form 제출 → @ModelAttribute
- 파일 업로드(FormData) → @ModelAttribute
- REST API(JSON 통신) → @RequestBody
특히 프론트엔드(Vue, React, Nuxt)와 API 서버를 분리해서 개발하는 경우에는 대부분 JSON을 사용하므로 @RequestBody를 사용하는 일이 더 많습니다.
반면 JSP, Thymeleaf와 같은 서버 사이드 렌더링 환경에서는 Form 전송이 많아 @ModelAttribute를 자주 사용하게 됩니다.
자주 묻는 질문(FAQ)
Q. @ModelAttribute는 POST 요청에서도 자동으로 동작하나요?
네. GET과 POST 모두 동작합니다. 단, Form 데이터나 Query Parameter를 대상으로 자동 바인딩합니다.
Q. JSON 요청인데 DTO가 비어 있습니다.
JSON 요청이라면 @ModelAttribute가 아니라 @RequestBody를 사용해야 합니다.
Q. @ModelAttribute를 생략해도 되나요?
복합 객체(DTO)라면 생략해도 동일하게 동작합니다.
Q. 파일 업로드도 @ModelAttribute를 사용하나요?
네. multipart/form-data 요청에서는 MultipartFile과 함께 @ModelAttribute를 사용하는 것이 일반적입니다.
마무리
@ModelAttribute는 GET 요청에서만 사용하는 기능이 아닙니다. POST 요청에서도 Form 데이터라면 Spring이 자동으로 DTO를 생성하고 값을 바인딩해 줍니다.
반대로 JSON 요청이라면 @RequestBody를 사용해야 하며, 두 어노테이션의 가장 큰 차이는 데이터를 어디에서 읽어오는지에 있습니다.
실무에서는 Form 데이터는 @ModelAttribute, **JSON 데이터는 @RequestBody**라는 기준만 기억해도 대부분의 상황에서 올바르게 사용할 수 있습니다.
'실무개발' 카테고리의 다른 글
| VS Code + Spring Boot + Gradle에서 외부 JAR 파일 적용하는 방법 (0) | 2026.07.24 |
|---|---|
| 해시(Hash), 암호화(Encryption), 전자서명(Digital Signature) 한 번에 이해하기 (0) | 2026.07.23 |
| 전자서명이란? 개발자가 반드시 알아야 할 개념과 구현 방식 정리 (0) | 2026.07.23 |
| Spring Boot REST API 예외 처리 ExceptionHandler 완벽 정리 (0) | 2026.07.19 |
| HTTP 요청 메서드 GET POST PUT PATCH DELETE 차이점 완벽 정리 (0) | 2026.07.19 |
