Request Mapping, Path Variables & Query Parameters
A list of every player is a start, but real clients ask more specific questions: "give me player 3", "only bowlers from Pune", "just the first two". These details travel inside the URL and the headers, and Spring MVC can hand them to your method as ordinary Java parameters, already converted to the right type.
In this lesson you will use @PathVariable, @RequestParam and @RequestHeader, choose between required, optional and default values, and see exactly what happens when a client sends something that does not fit.
Parts of a request URL
Take http://localhost:8080/api/players/3?city=pune&limit=2:
| Part | Example | Read with |
|---|---|---|
| Path | /api/players/3 | @GetMapping patterns and @PathVariable |
| Query string | city=pune&limit=2 | @RequestParam |
| Headers | Accept-Language: hi-IN | @RequestHeader |
Path variables identify which resource (player 3). Query parameters filter, sort or page a collection. That rule of thumb keeps your URLs predictable.
The controller
@RestController
@RequestMapping("/api/players")
public class PlayerController {
private final PlayerService service;
public PlayerController(PlayerService service) {
this.service = service;
}
@GetMapping
public List<Player> list(@RequestParam(required = false) PlayerRole role,
@RequestParam(required = false) String city,
@RequestParam(defaultValue = "10") int limit) {
return service.search(role, city).stream().limit(limit).toList();
}
@GetMapping("/{id}")
public ResponseEntity<Player> one(@PathVariable long id) {
return ResponseEntity.of(service.findById(id));
}
@GetMapping("/count")
public String count(@RequestHeader(name = "Accept-Language", defaultValue = "en") String language) {
int total = service.findAll().size();
return language.startsWith("hi") ? "Kul khiladi: " + total : "Total players: " + total;
}
}The service's search method keeps players whose role matches (when a role is given) and whose city matches, ignoring case (when a city is given). findById returns an Optional<Player>.
Path variables
{id} in the pattern is a placeholder. @PathVariable long id receives it, and Spring converts the text "3" to the number 3. Because the Java parameter has the same name as the placeholder, no extra attribute is needed (Spring Boot compiles with parameter names kept). If they differ, write @PathVariable("id") long playerId.
curl http://localhost:8080/api/players/3{"id":3,"name":"Kabir Shaikh","role":"ALL_ROUNDER","age":16,"city":"Mumbai","joinedOn":"2025-07-14"}ResponseEntity.of(optional) returns 200 with the player if the Optional has a value, and 404 with an empty body if not:
curl -i http://localhost:8080/api/players/99HTTP/1.1 404 Content-Length: 0 Date: Sun, 01 Nov 2026 18:06:35 GMT
If the text cannot be converted, Spring rejects the request before your method runs:
curl -i http://localhost:8080/api/players/abcHTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 01 Nov 2026 18:06:37 GMT
Connection: close
{"timestamp":"2026-11-01T18:06:37.422Z","status":400,"error":"Bad Request","path":"/api/players/abc"}The server log explains the reason: MethodArgumentTypeMismatchException: Method parameter 'id': Failed to convert value of type 'java.lang.String' to required type 'long'. A 400 status (Bad Request) means "the client sent something wrong", which is exactly right here.
Query parameters
@RequestParam reads ?name=value pairs. By default a request parameter is required; leaving it out gives 400. You can relax that in two ways:
required = false: the parameter may be missing and arrives asnull. Use an object type such asStringor an enum, never a primitive likeint, which cannot benull.defaultValue = "10": when missing, Spring uses the default (converted to the parameter type). This also makes it optional.
curl "http://localhost:8080/api/players?city=pune"
curl "http://localhost:8080/api/players?role=BOWLER"
curl "http://localhost:8080/api/players?limit=2"[{"id":1,"name":"Aarav Patil","role":"BATTER","age":15,"city":"Pune","joinedOn":"2025-06-02"},{"id":2,"name":"Priya Deshmukh","role":"BOWLER","age":14,"city":"Pune","joinedOn":"2025-06-09"}]
[{"id":2,"name":"Priya Deshmukh","role":"BOWLER","age":14,"city":"Pune","joinedOn":"2025-06-09"}]
[{"id":1,"name":"Aarav Patil","role":"BATTER","age":15,"city":"Pune","joinedOn":"2025-06-02"},{"id":2,"name":"Priya Deshmukh","role":"BOWLER","age":14,"city":"Pune","joinedOn":"2025-06-09"}]Put the URL in quotes when it contains ? or &, otherwise your shell may treat those characters specially. Spring converts role=BOWLER to the PlayerRole enum by its name, and the conversion is case-sensitive, so an unknown value is a 400:
curl "http://localhost:8080/api/players?role=CAPTAIN"{"timestamp":"2026-11-01T18:06:38.293Z","status":400,"error":"Bad Request","path":"/api/players"}A parameter that may repeat, such as ?city=Pune&city=Nashik, can be received as List<String>.
Headers
@RequestHeader works like @RequestParam but reads an HTTP header. Here the count endpoint answers in Hindi (in Latin script) when the client prefers it:
curl http://localhost:8080/api/players/count
curl -H "Accept-Language: hi-IN" http://localhost:8080/api/players/countTotal players: 4 Kul khiladi: 4
Notice that /api/players/count did not clash with /api/players/{id}. When several patterns match, Spring picks the most specific one: a literal segment (count) beats a variable ({id}).
More mapping options
- Several paths:
@GetMapping({"/", "/all"})maps both. - Patterns:
/files/{*path}captures the rest of the path;{id:\d+}restricts a variable with a regular expression, so/api/players/abcwould be a 404 instead of a 400. - Conditions:
params,headers,consumesandproducesattributes narrow a mapping, for example@PostMapping(consumes = "application/json"). - Matrix of methods: one path can have different methods for
GET,POST,PUTandDELETE; a method that is not mapped gives 405 Method Not Allowed.
Common mistakes
intfor an optional parameter.@RequestParam(required = false) int limitfails when the parameter is missing, becausenullcannot become anint. UseIntegeror adefaultValue.- Path variable names that do not match.
{playerId}in the path with@PathVariable long idfails at runtime. Keep the names identical or name the variable in the annotation. - Using query parameters for identity. Prefer
/api/players/3over/api/players?id=3for one resource; use query parameters for filters. - Forgetting quotes around URLs with
&. The shell splits the command and only part of the URL is sent.
Key points
@PathVariablereads{placeholders}from the path;@RequestParamreads the query string;@RequestHeaderreads headers.- Spring converts text to
long, enums, dates and other types; failed conversion is a 400 Bad Request. - Request parameters are required by default; use
required = false(with an object type) ordefaultValue. ResponseEntity.of(optional)gives 200 with a body or 404 without one.- The most specific mapping wins, so
/countand/{id}can live side by side.
Exercise
Add an endpoint GET /api/players/by-city/{city} that returns players from that city, sorted by age, with an optional query parameter minAge (default 0) that removes younger players. Show the request for Pune players aged 15 or above and the expected output, using the four sample players.
Show solution
@GetMapping("/by-city/{city}")
public List<Player> byCity(@PathVariable String city,
@RequestParam(defaultValue = "0") int minAge) {
return service.search(null, city).stream()
.filter(p -> p.age() >= minAge)
.sorted(Comparator.comparingInt(Player::age))
.toList();
}curl "http://localhost:8080/api/players/by-city/Pune?minAge=15"[{"id":1,"name":"Aarav Patil","role":"BATTER","age":15,"city":"Pune","joinedOn":"2025-06-02"}]Priya (14) is removed by minAge=15. by-city is a literal segment, so this mapping is more specific than /{id} and does not clash with it.
Quick Quiz
1. Which annotation reads 3 from /api/players/3?
Path variables come from {placeholders} in the mapping pattern.
2. A client calls /api/players/abc but the method expects long id. What status is returned?
The type conversion fails before your method runs, so Spring answers 400.
3. How do you make limit optional with a value of 10 when missing?
defaultValue supplies the value and makes the parameter optional.
4. /api/players/count and /api/players/{id} both exist. Which handles GET /api/players/count?
Spring chooses the most specific pattern.
Learn Spring Boot with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
