@RequestMapping
Maps requests to a controller class or method by path, HTTP method, parameters, headers, media types and API version.
@RequestMapping(path = "/api/things", method = RequestMethod.GET, produces = "application/json")- Added in Spring Boot Spring Framework 2.5
Parameters
| Parameter | Type | Description |
|---|---|---|
$valueoptional | String[]= {} | Alias for path. One or more URI patterns such as "/{id}". Combined with any class-level @RequestMapping path. |
$pathoptional | String[]= {} | The URI patterns this method handles. Same as value; use whichever reads better. |
$methodoptional | RequestMethod[]= {} | HTTP methods to match. Empty means every method, which is rarely what you want on a method. |
$paramsoptional | String[]= {} | Narrows the mapping by query parameters: "type", "!type" or "type=pdf". |
$headersoptional | String[]= {} | Narrows the mapping by request headers, for example "X-Feature=beta". |
$consumesoptional | String[]= {} | Media types the request body must have (checked against Content-Type). A mismatch gives 415. |
$producesoptional | String[]= {} | Media types the method can write (checked against Accept). No overlap gives 406. |
$versionoptional | String= "" | API version this mapping serves, such as "1.1" or "1.1+". Needs API versioning configured. |
$nameoptional | String= "" | A name for the mapping, used by MvcUriComponentsBuilder to build links. Rarely needed. |
Return value
n/aRegisters a handler mapping. On a class it sets a shared prefix and defaults; on a method it combines with the class-level values to decide which requests the method handles.
Errors
No match gives 404; a path match with the wrong method gives 405; bad Content-Type gives 415; unsatisfiable Accept gives 406. Two identical mappings fail at startup with an ambiguous mapping error.
Changelog
- PHP Spring Framework 7.0Added the version attribute for first-class API versioning.
@RequestMapping is the general mapping annotation. @GetMapping, @PostMapping and the others are composed versions of it with method fixed. In a modern API you use it mainly on the class, to give every method a common path prefix and shared defaults such as produces, and use the shortcut annotations on the methods.
How matching works
Spring combines the class-level and method-level values, then picks the most specific match. The path comes first, then the method, then params and headers conditions, then consumes against the request's Content-Type and produces against Accept. Each failure has its own status: 404 for no path, 405 for the wrong method, 415 for a body type you do not accept and 406 when you cannot produce what the client asked for. These come from the framework, so you rarely need code for them.
@RequestMapping("/api/invoices") // class: prefix
class InvoiceController {
@GetMapping("/{id}") // method: GET /api/invoices/{id}
Invoice one(@PathVariable long id) { ... }
}Path patterns use Spring's PathPattern syntax: {id} captures a segment, {id:\d+} adds a regular expression and {*rest} captures the remainder of the path. Trailing slashes are not matched by default, so /api/invoices/ is a different URL from /api/invoices.
API versions
Framework 7 added a version attribute. Once you configure a version strategy (for example spring.mvc.apiversion.use.header=API-Version), two methods can share a path and differ only by version. Using the attribute without any strategy configured stops the application at startup ("API version specified, but no ApiVersionStrategy configured"), so set the property first.
Mistakes people make
- A method-level
@RequestMappingwithoutmethodanswers GET, POST, DELETE and everything else. A read endpoint that also accepts DELETE is a security review finding waiting to happen. Use the shortcut annotations. - Two methods with identical mappings. The application refuses to start with an ambiguous mapping error, which is better than guessing, but it often shows up only after a merge.
- Putting
consumeson the class. It then applies to GET methods with no body, which is harmless but misleading; keep it on the methods that read a body.
Example
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
record Invoice(long id, String customer, long totalInPence) {}
(path = "/api/invoices", produces = MediaType.APPLICATION_JSON_VALUE)
public class InvoiceController {
// Class-level path and produces apply to every method below.
(method = RequestMethod.GET)
List<Invoice> list() {
return List.of(new Invoice(1, "Acme Ltd", 120_00));
}
(path = "/{id}", method = RequestMethod.GET)
Invoice one( long id) {
return new Invoice(id, "Acme Ltd", 120_00);
}
}
When to use it
- Giving every endpoint in a controller a common /api/v1/orders prefix
- Serving the same path as JSON or CSV depending on the Accept header
- Running two versions of an endpoint side by side during a client migration
Frequently asked questions
Should I use @RequestMapping or @GetMapping on methods?
Use @GetMapping and the other shortcuts on methods. They read better and cannot accidentally match every HTTP method. Keep @RequestMapping for the class-level prefix.
Does /api/items/ match /api/items?
No. Trailing slash matching has been off by default since Spring Framework 6. Configure a redirect at the proxy or map both paths explicitly if clients need it.
More examples
Choosing a method by query parameter
params narrows the mapping. Both methods share a path; Spring picks the one whose parameter is present. A request with neither parameter gets 400, because no method's conditions match.
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
record Book(String isbn, String title) {}
@RestController
@RequestMapping("/api/books/search")
public class BookSearchController {
// GET /api/books/search?isbn=978... picks this method
@GetMapping(params = "isbn")
List<Book> byIsbn(@RequestParam String isbn) {
return List.of(new Book(isbn, "Effective Java"));
}
// GET /api/books/search?title=java picks this one
@GetMapping(params = "title")
List<Book> byTitle(@RequestParam String title) {
return List.of(new Book("9780134685991", title));
}
}
Restricting body and response types
consumes is checked against Content-Type and produces against Accept. The framework answers 415 or 406 for you, before your code runs.
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
record ImportRequest(String source) {}
record ImportResult(int rows) {}
@RestController
@RequestMapping("/api/imports")
public class ImportController {
// Content-Type: text/xml -> 415 Unsupported Media Type
// Accept: text/csv only -> 406 Not Acceptable
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ImportResult start(@RequestBody ImportRequest request) {
return new ImportResult(0);
}
}
Two versions of one endpoint
The version attribute lets two methods share a path. The configured strategy reads the version from the request (here a header) and routes to the matching method.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
record AccountV1(long id, String name) {}
record AccountV2(long id, String firstName, String lastName) {}
// Needs a version strategy, for example
// spring.mvc.apiversion.use.header=API-Version in application.properties.
@RestController
@RequestMapping("/api/accounts")
public class AccountController {
@GetMapping(path = "/{id}", version = "1.0")
AccountV1 v1(@PathVariable long id) {
return new AccountV1(id, "Ada Lovelace");
}
@GetMapping(path = "/{id}", version = "2.0")
AccountV2 v2(@PathVariable long id) {
return new AccountV2(id, "Ada", "Lovelace");
}
}
Discussion