Request Mapping
Request mapping is how Spring MVC connects an incoming HTTP request to a handler method. The central annotation is @RequestMapping, and Spring Boot adds focused HTTP-verb shortcuts that make controllers far more readable. This page covers the full mapping surface: paths, verbs, content types, and conditional matching.
@RequestMapping basics
@RequestMapping maps a URL pattern (and optionally an HTTP method, headers, params, and media types) to a method. It works at both the class level (a shared prefix) and the method level.
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@RequestMapping(method = RequestMethod.GET)
public List<Order> all() { ... }
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Order one(@PathVariable Long id) { ... }
}
The class-level /api/orders is combined with each method-level path, so one resolves to GET /api/orders/{id}.
HTTP method shortcuts
Writing method = RequestMethod.GET everywhere is noisy. Spring provides composed annotations that bake in the verb. Prefer these in real code.
| Shortcut | Equivalent | HTTP verb |
|---|---|---|
@GetMapping | @RequestMapping(method = GET) | GET |
@PostMapping | @RequestMapping(method = POST) | POST |
@PutMapping | @RequestMapping(method = PUT) | PUT |
@PatchMapping | @RequestMapping(method = PATCH) | PATCH |
@DeleteMapping | @RequestMapping(method = DELETE) | DELETE |
The same controller, rewritten:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping
public List<Order> all() { ... }
@GetMapping("/{id}")
public Order one(@PathVariable Long id) { ... }
@PostMapping
public Order create(@RequestBody OrderRequest body) { ... }
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { ... }
}
Tip: Reserve
@RequestMappingfor the class-level prefix and any handler that genuinely needs to match multiple verbs. Use the verb shortcuts everywhere else.
Class- vs method-level paths
Paths combine. A class-level mapping sets the namespace; method-level mappings extend it.
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping // GET /api/v1/users
public List<User> list() { ... }
@GetMapping("/{id}/roles") // GET /api/v1/users/{id}/roles
public List<Role> roles(@PathVariable Long id) { ... }
}
You can match multiple paths on one method by passing an array:
@GetMapping({"/health", "/healthz"})
public String health() { return "OK"; }
consumes and produces
consumes restricts which request Content-Type a handler accepts; produces declares what it returns and participates in content negotiation against the client’s Accept header.
@PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Order> create(@RequestBody OrderRequest body) {
Order saved = service.create(body);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
A request with the wrong Content-Type yields 415 Unsupported Media Type; an unsatisfiable Accept header yields 406 Not Acceptable.
Request:
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: text/plain" \
-d "oops"
Output:
{
"timestamp": "2026-06-13T10:14:22.512+00:00",
"status": 415,
"error": "Unsupported Media Type",
"path": "/api/orders"
}
You can also offer multiple representations from one method using produces:
@GetMapping(value = "/{id}",
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public Order one(@PathVariable Long id) { ... }
headers and params conditions
@RequestMapping (and the shortcuts) can match only when specific request headers or query params are present, absent, or equal to a value. This is the basis of one common API-versioning strategy.
// Matches only when ?type=summary is present
@GetMapping(value = "/{id}", params = "type=summary")
public OrderSummary summary(@PathVariable Long id) { ... }
// Matches only when ?type=summary is absent
@GetMapping(value = "/{id}", params = "!type")
public Order full(@PathVariable Long id) { ... }
// Header-based selection
@GetMapping(value = "/{id}", headers = "X-API-Version=2")
public OrderV2 v2(@PathVariable Long id) { ... }
| Condition | Syntax | Meaning |
|---|---|---|
| Param present | params = "type" | ?type=... must exist |
| Param absent | params = "!type" | ?type must NOT exist |
| Param equals | params = "type=summary" | exact value match |
| Header equals | headers = "X-API-Version=2" | request header match |
Note: Param and header conditions are narrowing filters, not data binding. To read the value, still use
@RequestParamor@RequestHeader— see Path & Query Parameters.
Path patterns and wildcards
Spring uses PathPattern matching. Common tokens:
{id}— a single path variable.{*path}— captures the remaining path including slashes.?matches one char,*matches within a segment,**matches across segments (use sparingly).
@GetMapping("/files/{*relativePath}")
public Resource serve(@PathVariable String relativePath) { ... }
Pitfalls
- Do not duplicate the same verb + path across two handlers — Spring throws an ambiguous-mapping error at startup.
- A trailing-slash mismatch is not auto-tolerated in Spring Boot 3.x (trailing-slash matching was removed);
/ordersand/orders/are distinct, so register the canonical form. - Overusing
**patterns can create overlapping mappings that are hard to reason about.