EthicsComputerCourses
Join training +91 90532 10052

Request Mapping, Path Variables & Query Parameters

Spring Boot · Lesson 9 of 29 Intermediate 7 min read Updated

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:

PartExampleRead with
Path/api/players/3@GetMapping patterns and @PathVariable
Query stringcity=pune&limit=2@RequestParam
HeadersAccept-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

Java
@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.

Terminal
curl http://localhost:8080/api/players/3
Output
{"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:

Terminal
curl -i http://localhost:8080/api/players/99
Output
HTTP/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:

Terminal
curl -i http://localhost:8080/api/players/abc
Output
HTTP/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 as null. Use an object type such as String or an enum, never a primitive like int, which cannot be null.
  • defaultValue = "10": when missing, Spring uses the default (converted to the parameter type). This also makes it optional.
Terminal
curl "http://localhost:8080/api/players?city=pune"
curl "http://localhost:8080/api/players?role=BOWLER"
curl "http://localhost:8080/api/players?limit=2"
Output
[{"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:

Terminal
curl "http://localhost:8080/api/players?role=CAPTAIN"
Output
{"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:

Terminal
curl http://localhost:8080/api/players/count
curl -H "Accept-Language: hi-IN" http://localhost:8080/api/players/count
Output
Total 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/abc would be a 404 instead of a 400.
  • Conditions: params, headers, consumes and produces attributes narrow a mapping, for example @PostMapping(consumes = "application/json").
  • Matrix of methods: one path can have different methods for GET, POST, PUT and DELETE; a method that is not mapped gives 405 Method Not Allowed.

Common mistakes

  • int for an optional parameter. @RequestParam(required = false) int limit fails when the parameter is missing, because null cannot become an int. Use Integer or a defaultValue.
  • Path variable names that do not match. {playerId} in the path with @PathVariable long id fails at runtime. Keep the names identical or name the variable in the annotation.
  • Using query parameters for identity. Prefer /api/players/3 over /api/players?id=3 for 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

  • @PathVariable reads {placeholders} from the path; @RequestParam reads the query string; @RequestHeader reads 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) or defaultValue.
  • ResponseEntity.of(optional) gives 200 with a body or 404 without one.
  • The most specific mapping wins, so /count and /{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
Java
@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();
}
Terminal
curl "http://localhost:8080/api/players/by-city/Pune?minAge=15"
Output
[{"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?

2. A client calls /api/players/abc but the method expects long id. What status is returned?

3. How do you make limit optional with a value of 10 when missing?

4. /api/players/count and /api/players/{id} both exist. Which handles GET /api/players/count?

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