Spring MVC Request Mapping: Paths, Params and Bodies
Introduction
This tutorial explains how Spring MVC maps an HTTP request to a controller method and turns the parts of the request into the method's arguments. We will start with@RequestMapping and its shortcut annotations, and the attributes that narrow a mapping down. We will then take values from the path with @PathVariable - including patterns and regular expressions - and from the query string with @RequestParam: required, optional, defaulted and multi-valued parameters. Next we will read headers and cookies, bind a group of query parameters to a record, and read a JSON body with @RequestBody. Lastly, we will send requests that do not fit any mapping, and see the 400, 404, 405 and 415 responses Spring answers with.(This tutorial builds on Building a REST API with Spring Boot and @RestControllerClick to read tutorial: Building a REST API with Spring Boot and @RestController.)
@RequestMapping and its shortcuts
@RequestMapping is the general annotation: it maps requests by path and, optionally, by HTTP method and other conditions. Its shortcuts fix the method -
@GetMapping,@PostMapping,@PutMapping,@PatchMappingand@DeleteMapping.
consumes: the request'sContent-Typemust match, for exampleconsumes = "application/json".produces: the response type, matched against the request'sAcceptheader.paramsandheaders: a query parameter or header must be present (or absent, with!name), or have a value.version: an API version, new in Spring Framework 7 - covered in API Versioning in Spring Framework 7Click to read tutorial: API Versioning in Spring Framework 7 and Spring Boot 4.
@RequestMapping sets a prefix and defaults that every method in the controller inherits.
The example controller
One controller demonstrates each way of receiving data. The sections below go through it endpoint by endpoint, with real requests against the running application -MappingApp.java - one endpoint for each way of receiving request data
//MappingApp.java
package com.javabrahman.rest.mapping;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
record BookFilter(String author, Integer fromYear, Integer toYear) {
}
record OrderLine(String isbn, int quantity) {
}
record Order(String customer, List<OrderLine> lines) {
}
@RestController
@RequestMapping("/api")
class MappingController {
// 1. Path variables
@GetMapping("/books/{id}")
String bookById(@PathVariable long id) {
return "book by id: " + id;
}
@GetMapping("/books/isbn/{isbn:\\d{13}}") // only 13 digits match
String bookByIsbn(@PathVariable String isbn) {
return "book by ISBN: " + isbn;
}
@GetMapping("/authors/{author}/books/{year}")
String booksByAuthorAndYear(@PathVariable String author, @PathVariable int year) {
return "books by " + author + " published in " + year;
}
@GetMapping("/files/{*path}") // captures the rest of the path, slashes included
String file(@PathVariable String path) {
return "file path: " + path;
}
// 2. Query parameters
@GetMapping("/search")
String search(@RequestParam String q,
@RequestParam(defaultValue = "1") int page,
@RequestParam Optional<String> sort,
@RequestParam(name = "tag", required = false) List<String> tags) {
return "q=" + q + ", page=" + page + ", sort=" + sort.orElse("(none)") + ", tags=" + tags;
}
@GetMapping("/params")
Map<String, String> allParams(@RequestParam Map<String, String> params) {
return params;
}
// 3. Headers and cookies
@GetMapping("/greeting")
String greeting(@RequestHeader(name = "Accept-Language", defaultValue = "en") String language,
@RequestHeader("User-Agent") Optional<String> userAgent,
@CookieValue(name = "theme", required = false) String theme) {
return "language=" + language + ", user agent present=" + userAgent.isPresent() + ", theme cookie=" + theme;
}
// 4. Several query parameters bound to one record
@GetMapping("/books")
BookFilter filter(@ModelAttribute BookFilter filter) {
return filter;
}
// 5. A JSON request body
@PostMapping(path = "/orders", consumes = "application/json")
@ResponseStatus(HttpStatus.CREATED)
String placeOrder(@RequestBody Order order) {
int items = order.lines().stream().mapToInt(OrderLine::quantity).sum();
return "order for " + order.customer() + ": " + order.lines().size() + " lines, " + items + " items";
}
}
@SpringBootApplication
public class MappingApp {
public static void main(String[] args) {
SpringApplication.run(MappingApp.class, args);
}
}
Path variables
A{name} segment in the mapping captures that part of the path, and @PathVariable passes it to the method parameter of the same name, converted to the parameter's type -
OUTPUT of requests with path variables> curl -i http://localhost:8080/api/books/42
HTTP/1.1 200
Content-Length: 14
Content-Type: text/plain; charset=UTF-8
book by id: 42
> curl -i http://localhost:8080/api/books/isbn/9780134685991
HTTP/1.1 200
Content-Length: 27
Content-Type: text/plain; charset=UTF-8
book by ISBN: 9780134685991
> curl -i http://localhost:8080/api/books/isbn/12345
HTTP/1.1 404
Content-Type: application/json
{"timestamp":"2026-11-06T09:17:40.167Z","status":404,"error":"Not Found","path":"/api/books/isbn/12345"}
> curl -i http://localhost:8080/api/authors/bloch/books/2018
HTTP/1.1 200
Content-Length: 32
Content-Type: text/plain; charset=UTF-8
books by bloch published in 2018
> curl -i http://localhost:8080/api/files/docs/2026/guide.pdf
HTTP/1.1 200
Content-Length: 31
Content-Disposition: inline; filename=f.txt
Content-Type: text/plain; charset=UTF-8
file path: /docs/2026/guide.pdf
Explanation of the output
/api/books/42converted42to along.{isbn:\d{13}}adds a regular expression: the 13-digit ISBN matched, while12345did not match any mapping, so Spring answered404. Regular expressions keep different kinds of ids apart, and reject malformed ones before your code runs.- A path can hold several variables, each converted separately - a
Stringauthor and anintyear. {*path}captures everything that remains, slashes included - starting with the slash after/files- which suits file paths and similar hierarchical names.- The variable name comes from the method parameter's name; this needs the compiler's
-parametersoption, which Spring Boot's Maven and Gradle plugins switch on. Otherwise, name it explicitly:@PathVariable("id") long id.
Query parameters
@RequestParam reads parameters from the query string - the part after ? - or from a submitted HTML form. By default a parameter is required; defaultValue, required = false or an Optional make it optional. A parameter repeated in the URL binds to a List, and a Map receives all parameters -
OUTPUT of requests with query parameters> curl -i http://localhost:8080/api/search?q=java
HTTP/1.1 200
Content-Length: 38
Content-Type: text/plain; charset=UTF-8
q=java, page=1, sort=(none), tags=null
> curl -i http://localhost:8080/api/search?q=java&page=3&sort=title&tag=streams&tag=collections
HTTP/1.1 200
Content-Length: 55
Content-Type: text/plain; charset=UTF-8
q=java, page=3, sort=title, tags=[streams, collections]
> curl -i http://localhost:8080/api/search?page=2
HTTP/1.1 400
Content-Type: application/json
{"timestamp":"2026-11-06T09:17:47.555Z","status":400,"error":"Bad Request","path":"/api/search"}
> curl -i http://localhost:8080/api/params?category=java&level=beginner
HTTP/1.1 200
Content-Length: 38
Content-Type: application/json
{"category":"java","level":"beginner"}
Explanation of the output
- With only
q, the defaults applied: page 1 fromdefaultValue, an emptyOptionalforsort, andnullfor the tags. - The second request set every parameter; the two
tagvalues became a list. - Without the required
q, Spring answered400 Bad Requestbefore calling the method. - The
Mapparameter received every query parameter, which Spring then wrote back as a JSON object.
Headers and cookies
@RequestHeader and @CookieValue work the same way as @RequestParam, for request headers and cookies -
OUTPUT of requests with headers and a cookie> curl -i http://localhost:8080/api/greeting HTTP/1.1 200 Content-Length: 56 Content-Type: text/plain; charset=UTF-8 language=en, user agent present=false, theme cookie=null > curl -i http://localhost:8080/api/greeting -H "Accept-Language: fr-FR" -H "Cookie: theme=dark" HTTP/1.1 200 Content-Length: 59 Content-Type: text/plain; charset=UTF-8 language=fr-FR, user agent present=false, theme cookie=dark
Accept-Language fell back to its default, and the missing cookie gave null; with them, both values arrived. The User-Agent header was absent in both requests from this test client. Use headers for metadata about a request - language, tracing ids, client versions - and keep business data in the path, query or body.
Binding query parameters to a record
An endpoint with many optional filters would need a long list of@RequestParams. @ModelAttribute binds them to one object instead - here the BookFilter record, whose component names match the parameter names. (The annotation can even be left out for such a parameter.) -
OUTPUT of requests bound to a BookFilter record> curl -i http://localhost:8080/api/books?author=Bloch&fromYear=2010
HTTP/1.1 200
Content-Length: 48
Content-Type: application/json
{"author":"Bloch","fromYear":2010,"toYear":null}
> curl -i http://localhost:8080/api/books?toYear=2005&fromYear=1995
HTTP/1.1 200
Content-Length: 45
Content-Type: application/json
{"author":null,"fromYear":1995,"toYear":2005}
Integer, and null for the others. The order of the parameters in the URL does not matter. Filtering and paging are covered in Pagination, Sorting and FilteringClick to read tutorial: Pagination, Sorting and Filtering in Spring Boot REST APIs.
The request body
@RequestBody converts the request body into an object with the JSON message converter - here an Order record containing a list of OrderLine records. consumes = "application/json" limits the mapping to JSON requests -
OUTPUT of a request with a JSON body> curl -i -X POST http://localhost:8080/api/orders -H "Content-Type: application/json" -d '{"customer":"Nancy Smith","lines":[{"isbn":"9780134685991","quantity":2},{"isbn":"9781617293566","quantity":1}]}'
HTTP/1.1 201
Content-Length: 39
Content-Type: text/plain; charset=UTF-8
order for Nancy Smith: 2 lines, 3 items
@RequestBody parameter; it can be combined freely with path variables, query parameters and headers.
When a request does not fit
Spring rejects requests that do not match a mapping, or whose data cannot be converted, with a precise status code - before any of your code runs - OUTPUT of requests that do not fit any mapping> curl -i http://localhost:8080/api/books/abc
HTTP/1.1 400
Content-Type: application/json
{"timestamp":"2026-11-06T09:18:16.481Z","status":400,"error":"Bad Request","path":"/api/books/abc"}
> curl -i -X DELETE http://localhost:8080/api/books/42
HTTP/1.1 405
Allow: GET
Content-Type: application/json
{"timestamp":"2026-11-06T09:18:16.601Z","status":405,"error":"Method Not Allowed","path":"/api/books/42"}
> curl -i http://localhost:8080/api/books/42/
HTTP/1.1 404
Content-Type: application/json
{"timestamp":"2026-11-06T09:18:16.622Z","status":404,"error":"Not Found","path":"/api/books/42/"}
> curl -i -X POST http://localhost:8080/api/orders -H "Content-Type: text/plain" -d 'Two copies of Effective Java please'
HTTP/1.1 415
Accept: application/json
Content-Type: application/json
{"timestamp":"2026-11-06T09:18:16.697Z","status":415,"error":"Unsupported Media Type","path":"/api/orders"}
> curl -i -X POST http://localhost:8080/api/orders -H "Content-Type: application/json" -d '{"customer":"Nancy Smith","lines":['
HTTP/1.1 400
Content-Type: application/json
{"timestamp":"2026-11-06T09:18:16.818Z","status":400,"error":"Bad Request","path":"/api/orders"}
Explanation of the output
/api/books/abcmatched the{id}mapping, butabccannot become along:400 Bad Request.DELETE /api/books/42matched a path that only supportsGET:405 Method Not Allowed, with anAllowheader listing the methods that are supported./api/books/42/with a trailing slash did not match: since Spring Framework 6, trailing slashes are no longer ignored by default, so/books/42and/books/42/are different paths.- A
text/plainbody sent to a mapping thatconsumesJSON:415 Unsupported Media Type. - Malformed JSON could not be read:
400 Bad Request. - The bodies are Spring Boot's default error format; REST Error Handling with @ControllerAdvice and ProblemDetailClick to read tutorial: REST Error Handling with @ControllerAdvice and ProblemDetail shows how to return standard, more informative errors.
Summary
In this tutorial we first saw@RequestMapping, its method shortcuts and the attributes that narrow a mapping. We then captured path variables, including a regular expression and a catch-all {*path}, and read query parameters that were required, defaulted, optional, repeated, or collected into a map. Next we read headers and a cookie, bound several query parameters to a record with @ModelAttribute, and read a nested JSON body with @RequestBody. Lastly, we sent requests that did not fit and saw Spring answer 400, 404, 405 and 415. The next tutorial takes control of the response: ResponseEntity and HTTP status codesClick to read tutorial: ResponseEntity and HTTP Status Codes in Spring Boot.Spring REST API Tutorials
Building a REST API with Spring Boot and @RestControllerClick to read tutorial: Building a REST API with Spring Boot and @RestController ResponseEntity and HTTP Status Codes in Spring BootClick to read tutorial: ResponseEntity and HTTP Status Codes in Spring Boot Bean Validation in Spring Boot: @Valid and Custom ValidatorsClick to read tutorial: Bean Validation in Spring Boot: @Valid and Custom Validators REST Error Handling with @ControllerAdvice and ProblemDetailClick to read tutorial: REST Error Handling with @ControllerAdvice and ProblemDetail API Versioning in Spring Framework 7 and Spring Boot 4Click to read tutorial: API Versioning in Spring Framework 7 and Spring Boot 4 Pagination, Sorting and Filtering in Spring Boot REST APIsClick to read tutorial: Pagination, Sorting and Filtering in Spring Boot REST APIs Calling REST Services with RestClient and HTTP InterfacesClick to read tutorial: Calling REST Services with RestClient and HTTP Interfaces
Building a REST API with Spring Boot and @RestControllerClick to read tutorial: Building a REST API with Spring Boot and @RestController ResponseEntity and HTTP Status Codes in Spring BootClick to read tutorial: ResponseEntity and HTTP Status Codes in Spring Boot Bean Validation in Spring Boot: @Valid and Custom ValidatorsClick to read tutorial: Bean Validation in Spring Boot: @Valid and Custom Validators REST Error Handling with @ControllerAdvice and ProblemDetailClick to read tutorial: REST Error Handling with @ControllerAdvice and ProblemDetail API Versioning in Spring Framework 7 and Spring Boot 4Click to read tutorial: API Versioning in Spring Framework 7 and Spring Boot 4 Pagination, Sorting and Filtering in Spring Boot REST APIsClick to read tutorial: Pagination, Sorting and Filtering in Spring Boot REST APIs Calling REST Services with RestClient and HTTP InterfacesClick to read tutorial: Calling REST Services with RestClient and HTTP Interfaces