Spring MVC Annotations - Guide with Examples
Spring MVC is a Java framework for web applications and REST APIs. Its annotations handle routing, request data, validation, responses, and errors. Some commonly used annotations come from the broader Spring ecosystem or Jakarta Validation rather than Spring MVC itself.
1. Controller and mapping annotations
| Annotation | Purpose |
|---|---|
@Controller |
Defines an MVC controller, usually returning view names. |
@RestController |
Combines @Controller and @ResponseBody; usually returns JSON. |
@RequestMapping |
Maps requests by path, method, headers, content type, etc. |
@GetMapping |
Handles GET requests. |
@PostMapping |
Handles POST requests. |
@PutMapping |
Handles PUT requests. |
@PatchMapping |
Handles PATCH requests. |
@DeleteMapping |
Handles DELETE requests. |
Traditional MVC controller
@Controller
public class HomeController {
@GetMapping("/home")
public String home(Model model) {
model.addAttribute("message", "Welcome!");
return "home"; // e.g. a Thymeleaf home.html template
}
}
REST controller
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
public List<String> list() {
return List.of("John", "Mary", "David");
}
@GetMapping("/{id}")
public String get(@PathVariable Long id) {
return "User ID: " + id;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public User create(@RequestBody User user) {
return user;
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
// Delete the user in the service layer.
}
}
2. Request binding annotations
| Annotation | Purpose |
|---|---|
@PathVariable |
Reads a URL path variable. |
@RequestParam |
Reads a query string or form parameter. |
@RequestBody |
Deserializes the HTTP body into an object. |
@RequestHeader |
Reads a request header. |
@CookieValue |
Reads a cookie. |
@RequestPart |
Reads a multipart request part, including files. |
@ModelAttribute |
Binds request parameters to an object or adds data to the model. |
@RequestAttribute |
Reads a request attribute set by earlier server-side processing. |
@SessionAttribute |
Reads an existing session attribute. |
@MatrixVariable |
Reads matrix parameters in a path, when configured. |
Path and query parameters
@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") Long userId) {
return "User: " + userId;
}
@GetMapping("/search")
public String search(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(required = false) String category) {
return "Keyword: " + keyword + ", Page: " + page;
}
Example requests: GET /users/15 and GET /search?keyword=java&page=2.
JSON request body
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return user;
}
Example body:
{"name": "John", "email": "john@example.com"}
Spring typically uses Jackson to deserialize JSON.
Headers and cookies
@GetMapping("/headers")
public String headers(@RequestHeader("User-Agent") String agent) {
return agent;
}
@GetMapping("/session")
public String session(
@CookieValue(value = "sessionId", required = false) String id) {
return id != null ? id : "No session cookie";
}
Multipart upload
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(@RequestPart("file") MultipartFile file,
@RequestPart("metadata") DocumentMetadata metadata) {
return file.getOriginalFilename();
}
3. Response annotations
| Annotation | Purpose |
|---|---|
@ResponseBody |
Writes a method result to the response body. |
@ResponseStatus |
Sets a fixed response status. |
@ModelAttribute (method) |
Exposes data in the MVC model. |
@Controller
public class StatusController {
@GetMapping("/api/status")
@ResponseBody
public Map<String, String> status() {
return Map.of("status", "OK");
}
}
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
public User create(@RequestBody User user) {
return userService.save(user);
}
For dynamic statuses and headers, use ResponseEntity<T> (a class, not an annotation):
@GetMapping("/users/{id}")
public ResponseEntity<User> find(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
4. Validation annotations
@Valid is from Jakarta Validation; @Validated is from Spring. Constraints such as @NotBlank are from Jakarta Validation.
| Annotation | Purpose |
|---|---|
@Valid |
Triggers validation of an object. |
@Validated |
Triggers Spring validation, optionally with groups. |
@NotNull |
Value must not be null. |
@NotBlank |
String must contain non-whitespace characters. |
@NotEmpty |
String, array, map, or collection must not be empty. |
@Size |
Limits string length or collection size. |
@Min, @Max |
Set numeric limits. |
@Email |
Checks email format. |
@Pattern |
Checks a regular expression. |
@Positive |
Requires a positive number. |
@Past, @Future |
Validate temporal values. |
import jakarta.validation.constraints.*;
public record CreateUserRequest(
@NotBlank @Size(min = 3, max = 100) String name,
@NotBlank @Email String email,
@NotNull @Min(18) Integer age
) {}
@PostMapping("/users")
public ResponseEntity<CreateUserRequest> create(
@Valid @RequestBody CreateUserRequest request) {
return ResponseEntity.status(HttpStatus.CREATED).body(request);
}
An invalid request body generally produces HTTP 400. The validation starter (spring-boot-starter-validation) is normally needed in Spring Boot applications.
5. Exception handling annotations
| Annotation | Purpose |
|---|---|
@ExceptionHandler |
Handles selected exception types. |
@ControllerAdvice |
Shares controller advice across controllers. |
@RestControllerAdvice |
Combines @ControllerAdvice with @ResponseBody. |
@ResponseStatus on exception classes |
Associates an exception with an HTTP status. |
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, String>> badRequest(
IllegalArgumentException ex) {
return ResponseEntity.badRequest()
.body(Map.of("error", ex.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> validation(
MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining("; "));
return ResponseEntity.badRequest().body(Map.of("error", message));
}
}
6. Model, binding, and session annotations
| Annotation | Purpose |
|---|---|
@ModelAttribute |
Binds form fields and/or adds model attributes. |
@InitBinder |
Configures binding and validation. |
@SessionAttributes |
Persists selected MVC model attributes in the session. |
@SessionAttribute |
Reads an existing session attribute. |
@Controller
@RequestMapping("/users")
public class RegistrationController {
@PostMapping("/register")
public String register(@Valid @ModelAttribute("user") UserForm form,
BindingResult result) {
if (result.hasErrors()) {
return "registration";
}
return "redirect:/users/success";
}
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setAllowedFields("name", "email");
}
}
Keep BindingResult immediately after the validated model argument.
@Controller
@SessionAttributes("cart")
public class CartController {
@ModelAttribute("cart")
public Cart initializeCart() {
return new Cart();
}
@PostMapping("/cart/complete")
public String complete(SessionStatus status) {
status.setComplete();
return "redirect:/checkout/success";
}
}
7. CORS annotation
@CrossOrigin configures cross-origin requests at the controller or method level.
@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "https://frontend.example.com")
public class ProductController {
@GetMapping
public List<Product> list() {
return productService.findAll();
}
}
For application-wide rules, consider configuring CORS via WebMvcConfigurer. CORS is not a substitute for authentication or authorization.
8. Other Spring annotations commonly used with MVC
These are not all Spring MVC annotations, but they commonly appear in MVC projects.
| Annotation | Purpose |
|---|---|
@Component |
Registers a general-purpose component. |
@Service |
Marks a service-layer component. |
@Repository |
Marks a persistence-layer component. |
@Autowired |
Injects a dependency. |
@Qualifier |
Chooses a bean when multiple candidates exist. |
@Primary |
Makes a bean the preferred candidate. |
@Configuration |
Declares configuration. |
@Bean |
Defines a Spring-managed bean. |
@Value |
Injects a configuration value. |
@Profile |
Limits activation to certain profiles. |
@Transactional |
Defines a transaction boundary. |
Constructor injection is generally preferred; a single constructor does not need @Autowired.
@Service
public class UserService {
public List<String> findAll() {
return List.of("John", "Mary");
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping
public List<String> findAll() {
return userService.findAll();
}
}
9. Complete miniature REST API example
// UserRequest.java
import jakarta.validation.constraints.*;
public record UserRequest(
@NotBlank String name,
@NotBlank @Email String email
) {}
// UserResponse.java
public record UserResponse(Long id, String name, String email) {}
// UserService.java
import org.springframework.stereotype.Service;
@Service
public class UserService {
public UserResponse findById(Long id) {
return new UserResponse(id, "John", "john@example.com");
}
public UserResponse create(UserRequest request) {
// Example only: persist to a database in a real application.
return new UserResponse(1L, request.name(), request.email());
}
}
// UserController.java
import jakarta.validation.Valid;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping(value = "/api/users",
produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody UserRequest request) {
UserResponse created = userService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}
This sample omits persistence, authentication, and missing-user handling for brevity.
10. Learning order
@RestController,@RequestMapping@GetMapping,@PostMapping,@PutMapping,@PatchMapping,@DeleteMapping@PathVariable,@RequestParam,@RequestBody@Valid,@Validated, Jakarta constraints@RestControllerAdvice,@ExceptionHandler@ResponseStatus,@ResponseBody,ResponseEntity@Service,@Repository, constructor injection@Transactional@CrossOrigin@ModelAttribute,@InitBinder
11. Simplified Spring MVC request lifecycle
HTTP request
|
v
DispatcherServlet (front controller)
|
v
Handler mapping + argument binding + validation
|
v
@Controller / @RestController
|
v
@Service (business logic)
|
v
@Repository / database (if needed)
|
v
Controller result -> response conversion / view rendering
|
v
HTTP response
Exceptions may be intercepted by MVC exception resolvers and @ExceptionHandler methods.