Spring MVC provides robust mechanisms for binding incoming client request data to handler method parameters. Understanding these techniques is crucial for building flexible and maintainable web applications.
1. Direct Parameter Mapping
Spring MVC can automatically bind request parameters to handler method arguments when their names match. This is the most straightforward approach for simple form submissions or query parameters.
Consider a basic HTML form:
<form action="/authenticate" method="post">
<label for="loginId">Username:</label>
<input type="text" id="loginId" name="username"/>
<label for="pwd">Password:</label>
<input type="password" id="pwd" name="password"/>
<input type="submit" value="Login"/>
</form>
The corresponding controller method would look like this:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class AuthController {
@RequestMapping("/authenticate")
@ResponseBody
public String processLogin(String username, String password) {
// Logic to validate username and password
return "Authentication successful for user: " + username;
}
}
2. Binding to Complex Objects (POJOs)
Spring MVC extends direct parameter mapping to complex Java objects (Plain Old Java Objects). If a request parameter name matches a property name within a POJO, Spring will attempt to bind the value. This also applies to nested objects, lists, and maps.
Example Form Data Structure:
<form action="/submitProfile" method="post">
<label>User Name:</label><input type="text" name="name"/><br/>
<label>User Email:</label><input type="email" name="email"/><br/>
<label>Primary Address - Street:</label><input type="text" name="primaryAddress.street"/><br/>
<label>Primary Address - City:</label><input type="text" name="primaryAddress.city"/><br/>
<label>Phone Numbers[0]:</label><input type="text" name="phoneNumbers[0]"/><br/>
<label>Phone Numbers[1]:</label><input type="text" name="phoneNumbers[1]"/><br/>
<label>Custom Settings (key: 'theme'):</label><input type="text" name="customSettings['theme']"/><br/>
<label>Custom Settings (key: 'language'):</label><input type="text" name="customSettings['language']"/><br/>
<input type="submit" value="Save Profile"/>
</form>
And the corresponding Java classes and controller method:
// UserProfile.java
public class UserProfile {
private String name;
private String email;
private Address primaryAddress;
private java.util.List<String> phoneNumbers;
private java.util.Map<String, String> customSettings;
// Getters and Setters
// ... (omitted for brevity)
@Override
public String toString() { /* ... */ return "UserProfile{...}"; }
}
// Address.java
public class Address {
private String street;
private String city;
// Getters and Setters
// ... (omitted for brevity)
@Override
public String toString() { /* ... */ return "Address{...}"; }
}
// ProfileController.java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ProfileController {
@PostMapping("/submitProfile")
@ResponseBody
public String handleProfileSubmission(UserProfile profile) {
System.out.println("Received Profile: " + profile);
return "Profile saved successfully!";
}
}
3. Handling Query Parameters from URLs
For GET requests, parameters often appear in the URL's query string. Spring MVC binds these to method parameters in the same way as form data.
Example URL: /products?page=2&size=25
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ProductController {
@GetMapping("/products")
@ResponseBody
public String listProducts(Integer page, Integer size) {
return "Displaying products - Page: " + page + ", Items per page: " + size;
}
}
Important Note on Primitive vs. Wrapper Types: When binding parameters that might be absent from the request (e.g., optional query parameters), it's crucial to use Java wrapper types (like Integer, Long, Boolean) instead of primitive types (int, long, boolean). If a primitive type is used and the parameter is missing, Spring MVC will attempt to convert null to the primitive type, resulting in a TypeMismatchException. Wrapper types gracefully handle null values.
4. @RequestParam Annotation for Explicit Mapping
To explicitly map a request parameter to a method argument, or when the argument name differs from the request parameter name, use the @RequestParam annotation.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class SearchController {
@GetMapping("/search")
@ResponseBody
public String performSearch(@RequestParam("q") String queryTerm, @RequestParam("cat") String category) {
return "Searching for '" + queryTerm + "' in category '" + category + "'";
}
}
In this example, q from request maps to queryTerm, and cat maps to category.
5. Setting Default Values with @RequestParam
The defaultValue attribute of @RequestParam allows you to specify a fallback value if request parameter is not present.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class PagingController {
@GetMapping("/items")
@ResponseBody
public String getItemsPaginated(
@RequestParam(value = "page", defaultValue = "0") Integer currentPage,
@RequestParam(value = "limit", defaultValue = "10") Integer itemsPerPage) {
return "Fetching items - Current Page: " + currentPage + ", Limit: " + itemsPerPage;
}
}
6. Marking Parameters as Optional or Required
The required attribute of @RequestParam (defaulting to true) dictates whether a parameter must be present in the request. Setting it to false makes the parameter optional.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class ReportController {
@GetMapping("/report")
@ResponseBody
public String generateReport(
@RequestParam("reportType") String type,
@RequestParam(value = "startDate", required = false) String start,
@RequestParam(value = "endDate", required = false) String end) {
String dateRange = (start != null && end != null) ? " from " + start + " to " + end : " (no date range specified)";
return "Generating " + type + " report" + dateRange;
}
}
7. Path Variable Binding with @PathVariable
@PathVariable is used to bind a URI template variable to a method argument. This is commonly used for RESTful endpoints where resource identifiers are part of the URL path.
Example URL: /users/123/profile
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
@GetMapping("/users/{userId}/profile")
@ResponseBody
public String getUserProfile(@PathVariable("userId") Long id) {
return "Retrieving profile for User ID: " + id;
}
}
8. Handling JSON Payloads with @RequestBody
For POST or PUT requests containing a JSON or XML payload in the request body, the @RequestBody annotation is used. Spring MVC uses an appropriate HttpMessageConverter (e.g., Jackson for JSON) to deserialize the body into a Java object.
Client-side AJAX request sending JSON:
fetch('/api/createUser', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
firstName: 'Jane',
lastName: 'Doe',
email: 'jane.doe@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));
Controller method to receive the JSON:
// UserData.java
public class UserData {
private String firstName;
private String lastName;
private String email;
// Getters and Setters, toString()
}
// ApiController.java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class ApiController {
@PostMapping("/api/createUser")
@ResponseBody
public String createNewUser(@RequestBody UserData newUser) {
System.out.println("Received new user: " + newUser.getFirstName() + " " + newUser.getLastName());
return "User " + newUser.getFirstName() + " created.";
}
// Can also bind to a Map if the structure is dynamic
@PostMapping("/api/processDynamicData")
@ResponseBody
public String processDynamicData(@RequestBody java.util.Map<String, Object> dataMap) {
System.out.println("Received dynamic data: " + dataMap);
return "Processed dynamic data.";
}
}
9. Returning JSON Responses with @ResponseBody
The @ResponseBody annotation tells Spring MVC to write the return value of a controller method directly to the HTTP response body. An HttpMessageConverter (like Jackson) handles the serialization, typical to JSON, when the Content-Type header is appropriately set (e.g., application/json).
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class DataController {
// Assume Product class has relevant fields and getters
private static class Product { /* ... */ }
@GetMapping("/api/product/{id}")
@ResponseBody
public Product getProductDetails(@PathVariable Long id) {
// In a real application, fetch product from a service/database
Product product = new Product(); // Placeholder
// product.setId(id); product.setName("Example Product");
return product;
}
}
10. Accessing HTTP Headers with @RequestHeader
Use @RequestHeader to bind the value of an HTTP request header to a method parameter.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class HeaderController {
@GetMapping("/info")
@ResponseBody
public String getRequestInfo(@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "Accept-Language", required = false) String language) {
return "User Agent: " + userAgent + ", Preferred Language: " + (language != null ? language : "N/A");
}
}
11. Reading Cookie Values with @CookieValue
The @CookieValue annotation extracts a specific HTTP cookie's value and binds it to a method parameter.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.stereotype.Controller;
@Controller
public class CookieController {
@GetMapping("/cookieData")
@ResponseBody
public String retrieveCookieInfo(@CookieValue(value = "JSESSIONID", required = false) String sessionId) {
if (sessionId != null) {
return "Session ID from cookie: " + sessionId;
} else {
return "JSESSIONID cookie not found.";
}
}
}
12. Pre-populating Model Attributes with @ModelAttribute
@ModelAttribute can be used in two primary ways:
-
Method-level: When applied to a method, this method executes before any
@RequestMappinghandler methods in the same controller. Its purpose is to prepare model attributes that are needed by handler methods or views. The return value of such a method is added to the model.// Account.java (simplified) public class Account { private String id; private String status; // Getters and Setters public String toString() { return "Account{id='" + id + "', status='" + status + "'}"; } } @Controller public class AccountController { // This method runs before 'viewAccount' and 'updateAccount' // It creates an Account object and makes it available in the model @ModelAttribute("accountInfo") public Account setupAccount(@RequestParam(value = "accountId", required = false) String accountId) { Account acc = new Account(); if (accountId != null) { acc.setId(accountId); acc.setStatus("Active"); // Default status for existing account } else { acc.setId("NEW_ACCOUNT"); acc.setStatus("Pending"); // Default for new account } System.out.println("ModelAttribute method executed: " + acc); return acc; } @GetMapping("/account/{accountId}") @ResponseBody public String viewAccount(@PathVariable String accountId, @ModelAttribute("accountInfo") Account accountFromModel) { // 'accountFromModel' is the same instance returned by setupAccount (if accountId matched) return "Viewing account: " + accountFromModel; } @PostMapping("/account/update") @ResponseBody public String updateAccount(@ModelAttribute("accountInfo") Account accountFromRequest) { // 'accountFromRequest' will have properties populated from request parameters, // potentially overriding initial values from setupAccount if names match. System.out.println("Updating account: " + accountFromRequest); return "Account updated: " + accountFromRequest.getId(); } } -
Parameter-level: When applied to a handler method parameter, it indicates that the argument should be retrieved from the model. If it's not found, it will be instantiated and populated from request parameters. After population, it's added to the model.
A
@ModelAttributemethod can also bevoidand populate the model explicitly:import org.springframework.ui.Model; import java.util.Map; @Controller public class AnotherAccountController { @ModelAttribute public void populateDefaultAccount(Model model) { Account defaultAcc = new Account(); defaultAcc.setId("DEFAULT_ID"); defaultAcc.setStatus("Initialized"); model.addAttribute("initialAccount", defaultAcc); System.out.println("Populated model with initial account: " + defaultAcc); } @GetMapping("/showDefaultAccount") @ResponseBody public String displayDefaultAccount(@ModelAttribute("initialAccount") Account account) { return "Displayed default account: " + account; } }
13. Managing Session-scoped Attributes with @SessionAttributes
Annotating a controller class with @SessionAttributes specifies which model attributes (by name or type) should be transparently stored in the HTTP session between requests. This is useful for maintaining state across a multi-step workflow.
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.bind.annotation.ResponseBody;
// Assume UserPreferences class with fields like 'theme', 'language'
public class UserPreferences {
private String theme;
private String language;
// Getters/Setters, toString()
public String toString() { return "UserPreferences{theme='" + theme + "', language='" + language + "'}"; }
}
@Controller
@SessionAttributes({"userPrefs", "someStatusMessage"})
public class UserSessionController {
@GetMapping("/setupPreferences")
@ResponseBody
public String configurePreferences(Model model) {
UserPreferences prefs = new UserPreferences();
prefs.setTheme("dark");
prefs.setLanguage("en-US");
model.addAttribute("userPrefs", prefs);
model.addAttribute("someStatusMessage", "Preferences initialized");
return "User preferences initialized and stored in session.";
}
@GetMapping("/showPreferences")
@ResponseBody
public String viewPreferences(ModelMap modelMap) {
// Retrieve 'userPrefs' and 'someStatusMessage' from the session
UserPreferences prefs = (UserPreferences) modelMap.get("userPrefs");
String status = (String) modelMap.get("someStatusMessage");
return "Current preferences: " + (prefs != null ? prefs.toString() : "N/A") + ", Status: " + (status != null ? status : "N/A");
}
@GetMapping("/clearSessionData")
@ResponseBody
public String invalidateSessionAttributes(SessionStatus sessionStatus) {
// Mark the current handler's session attributes as complete,
// causing them to be removed from the session.
sessionStatus.setComplete();
return "Session attributes cleared.";
}
}