When to Use @RequestParam, @RequestBody, and @ModelAttribute in Spring MVC
This guide explains how Spring MVC binds incoming HTTP data to controller method parameters with @RequestParam, @RequestBody, and @ModelAttribute, when each is appropriate, and how they differ in behavior and data sources.
@RequestParam
Purpose
- Read simple request parameters encoded as application/x-www-form-urlencoded
- Works for query strings in GET and form fields in POST
- Values ultimately come from HttpServletRequest#getParameter(...)
Example: GET request with query parameters
HTML (GET form)
<form action="/catalog/search" method="get">
<div>
<label>Keyword: <input type="text" name="q" /></label>
</div>
<div>
<label>Page: <input type="number" name="page" /></label>
</div>
<button type="submit">Search</button>
</form>
Controller
@Controller
@RequestMapping("/catalog")
public class CatalogController {
@GetMapping("/search")
public String search(
@RequestParam("q") String keyword,
@RequestParam(name = "page", required = false, defaultValue = "1") int pageNo) {
System.out.println("@RequestParam demo");
System.out.println("q=" + keyword);
System.out.println("page=" + pageNo);
return "results";
}
}
Equivalent approaches
- Without @RequestParam when parameter names match request parameter names
@GetMapping("/search")
public String search(String q, Integer page) {
System.out.println("q=" + q);
System.out.println("page=" + page);
return "results";
}
- Using HttpServletRequest explicitly
@GetMapping("/search")
public String search(HttpServletRequest request) {
String q = request.getParameter("q");
String page = request.getParameter("page");
System.out.println("q=" + q);
System.out.println("page=" + page);
return "results";
}
POST works the same way for application/x-www-form-urlencoded bodies
@PostMapping("/update-profile")
public String updateProfile(
@RequestParam("displayName") String displayName,
@RequestParam("email") String email) {
// parameters come from form fields named displayName and email
return "profile";
}
Notes
- Spring converts Strings to target types using its ConversionService
- @RequestParam targets key/value pairs in the parameter map; it does not read raw request bodies beyond what the servlet container exposes in getParameter(...)
@RequestBody
Purpose
- Read the raw HTTP request body (HttpEntity) and convert it with HttpMessageConverters
- Typically used with JSON, XML, or other non-form encodings (e.g., application/json)
- Common with POST/PUT/PATCH. GET bodies are uncommon and usually unsupported by clients and servers
Example: Bind JSON to a POJO and return an object
@RestController
@RequestMapping("/books")
public class BookController {
public static class TitleLookup {
private String title;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}
public static class BookDto {
private String title;
private String author;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
}
@PostMapping("/find-by-title")
public BookDto findByTitle(@RequestBody TitleLookup payload) {
System.out.println("payload: " + payload.getTitle());
String name = payload.getTitle();
// pretend to query a service
BookDto result = new BookDto();
result.setTitle(name);
result.setAuthor("Unknown");
return result;
}
}
Client example (JSON POST)
fetch('/books/remove', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nickname: 'alice', itemIds: '10,11,42' })
}).then(r => r.json());
Controller receiving a JSON object into a Map
@RestController
@RequestMapping("/books")
public class BookMutationController {
private final BookService bookService;
private final UserService userService;
public BookMutationController(BookService bookService, UserService userService) {
this.bookService = bookService;
this.userService = userService;
}
@PostMapping("/remove")
public void remove(@RequestBody Map<String, String> body) {
String csvIds = body.get("itemIds");
String nickname = body.get("nickname");
String[] pieces = csvIds != null ? csvIds.split(",") : new String[0];
Integer userId = userService.findUserIdByNickname(nickname);
for (String p : pieces) {
if (p == null || p.isBlank()) continue;
int id = Integer.parseInt(p.trim());
bookService.deleteForUser(id, userId);
}
}
}
Typed variant: Map to a DTO with a list
public static class RemoveRequest {
private String nickname;
private List<Integer> itemIds;
public String getNickname() { return nickname; }
public void setNickname(String nickname) { this.nickname = nickname; }
public List<Integer> getItemIds() { return itemIds; }
public void setItemIds(List<Integer> itemIds) { this.itemIds = itemIds; }
}
@PostMapping("/remove2")
public void remove2(@RequestBody RemoveRequest req) {
Integer userId = userService.findUserIdByNickname(req.getNickname());
for (Integer id : req.getItemIds()) {
bookService.deleteForUser(id, userId);
}
}
Notes
- Requires Content-Type to match a configured HttpMessageConverter (e.g., application/json)
- The body is parsed by the converter and bound to the method parameter
- Not intended for form-encoded fields unless you deliberately send JSON; for stadnard forms perfer @RequestParam/@ModelAttribute
@ModelAttribute
Purpose
- Bind form-encoded fields (application/x-www-form-urlencoded) to a Java object and place it in the model
- Ideal for HTML form submissions to populate complex objects
HTML (POST form)
<form action="/users/register" method="post">
<div>
<label>User ID: <input type="number" name="id" /></label>
</div>
<div>
<label>Username: <input type="text" name="username" /></label>
</div>
<div>
<label>Password: <input type="password" name="password" /></label>
</div>
<button type="submit">Submit</button>
</form>
Model class
public class UserForm {
private Integer id;
private String username;
private String password;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
Controller
@Controller
@RequestMapping("/users")
public class UserController {
@PostMapping("/register")
public String register(@ModelAttribute UserForm form) {
System.out.println("id=" + form.getId());
System.out.println("username=" + form.getUsername());
System.out.println("password=" + form.getPassword());
return "ok";
}
}
Notes
- Field names in the form must match bean property names for proper binding
- @ModelAttribute is also usable on method parameters without the annotation in many cases; Spring assumes complex types should be bound from the model/request parameters
Choosing Between Them
- application/x-www-form-urlencoded
- Single or small set of simple values: @RequestParam
- Populate a object graph: @ModelAttribute
- multipart/form-data (file uploads)
- Use @RequestParam for simple fields, MultipartFile/@RequestPart for files; @RequestBody is not used for multipart
- application/json, application/xml, or arbitrary body payloads
- Use @RequestBody for binding the HTTP antity body
Additional details
- @RequestParam uses the parameter map (query string + form fields) and Spring’s type conversion mechanism
- @RequestBody uses HttpMessageConverters to deserialize the request body
- @ModelAttribute uses data binding from request parameters to populate an object and adds it to the model