Request Mapping & Parameters

Reviewed & published by Brayan K

Bind data from the request: @PathVariable for path segments, @RequestParam for query strings, @RequestBody for JSON payloads, and the right status codes for each operation.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

Finish Building REST Controllers first — this lesson builds on @RestController, @GetMapping/@PostMapping and ResponseEntity.

What You'll Learn

The Big Idea — Filling Out a Delivery Form

💡 Analogy: A request is like a delivery form. The address line (path) names exactly which house — that's your @PathVariable. The delivery options ticked at the bottom (query string) are your @RequestParam. The parcel itself (the body) is your @RequestBody. Spring reads the form and hands each part to the right method parameter.

Status codes are the courier's receipt: 201 "delivered & created", 404 "no such address", 204 "done, nothing to hand back".

1️⃣ Path Variables with @PathVariable

A templated segment like /users/{id} is captured with @PathVariable. Use this to identify a specific resource — the path is part of its identity.

// (Conceptual — needs Spring Boot web on the classpath to run.)
package com.example.demo;

import org.springframework.web.bind.annotation.*;

record User(long id, String name) {}

@RestController
@RequestMapping("/users")
public class UserController {

    // GET /users/42  ->  {id} is captured by @PathVariable.
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return new User(id, "User-" + id);
    }
}

2️⃣ Query Parameters with @RequestParam

@RequestParam binds query-string values like ?q=ann&page=2. Add required=false or defaultValue to make a parameter optional.

package com.example.demo;

import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/users")
public class UserSearchController {

    // GET /users/search?q=ann&page=2
    // @RequestParam binds the query string; defaultValue makes 'page' optional.
    @GetMapping("/search")
    public List<String> search(
            @RequestParam String q,
            @RequestParam(defaultValue = "1") int page) {
        return List.of("hit for '" + q + "' on page " + page);
    }
}

3️⃣ Request Bodies with @RequestBody

For POST/PUT, @RequestBody deserialises the JSON body into a record or POJO via Jackson. Pair it with a 201 Created status for new resources.

package com.example.demo;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

record CreateUser(String name, String email) {}
record User(long id, String name, String email) {}

@RestController
@RequestMapping("/users")
public class UserCreateController {

    // POST /users  with a JSON body -> @RequestBody deserialises it into the record.
    @PostMapping
    public ResponseEntity<User> create(@RequestBody CreateUser input) {
        User saved = new User(101, input.name(), input.email());
        // 201 Created is the conventional status for a successful POST.
        return ResponseEntity.status(HttpStatus.CREATED).body(saved);
    }
}
package com.example.demo;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserStatusController {

    // @ResponseStatus fixes the success status without using ResponseEntity.
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)   // 201 on success
    public String create(@RequestBody String name) {
        return "created: " + name;
    }

    // A handler can also bind a header value.
    @GetMapping("/whoami")
    public String whoami(@RequestHeader("X-User") String user) {
        return "header says: " + user;
    }
}

4️⃣ The Binding, Made Concrete

Here is what each binding produces, simulated in plain Java so the path / query / body distinction is unmistakable.

import java.util.*;

public class Main {
    // Simulate the binding Spring does: path id, query param, and a "body".
    static String getUser(long id) { return "{\"id\":" + id + ",\"name\":\"User-" + id + "\"}"; }
    static String search(String q, int page) { return "search q=" + q + " page=" + page; }
    static String create(Map<String,String> body) { return "created " + body.get("name"); }

    public static void main(String[] args) {
        // GET /users/42                -> @PathVariable Long id = 42
        System.out.println(getUser(42));
        // GET /users/search?q=ann&page=2 -> @RequestParam q="ann", page=2
        System.out.println(search("ann", 2));
        // POST /users  {"name":"Ada"}   -> @RequestBody -> object
        System.out.println(create(Map.of("name", "Ada")));
    }
}
// 🎯 YOUR TURN — replace each ___ using the hint beside it.
import java.util.*;

public class Main {
    record Route(String method, String pattern) {}

    static String describe(Route r) {
        List<String> vars = new ArrayList<>();
        for (String seg : r.pattern().split("/")) {
            // 1) A {name} segment is a path variable; anything else is literal.
            if (seg.startsWith("{") && seg.___("}")) vars.add(seg.substring(1, seg.length() - 1));  // 👉 replace ___ with endsWith
        }
        String base = r.method() + " " + r.pattern();
        // 2) No braces anywhere means nothing is captured.
        return vars.___() ? base + " (no path variables)" : base + " captures " + vars;  // 👉 replace ___ with isEmpty
    }

    static int specificity(Route r) {
        int score = 0;
        for (String seg : r.pattern().split("/")) {
            if (seg.isEmpty()) continue;
            // 3) A literal segment is worth more than a variable one. That is
            //    the whole reason /books/featured wins over /books/{id}.
            score += seg.startsWith("{") ? 1 : ___;   // 👉 replace ___ with 2
        }
        return score;
    }

    public static void main(String[] args) {
        List<Route> routes = new ArrayList<>(List.of(
            new Route("GET", "/books/{id}"),
            new Route("GET", "/books/featured"),
            new Route("GET", "/books")
        ));

        for (Route r : routes) System.out.println(describe(r));

        // 4) Highest score first, so the most specific pattern is tried first.
        routes.sort(Comparator.comparingInt(Main::specificity).___());   // 👉 replace ___ with reversed
        System.out.println("Most specific first:");
        for (Route r : routes) System.out.println("  " + r.pattern() + " (score " + specificity(r) + ")");

        // 5) After sorting, the winner is simply the first one.
        System.out.println("/books/featured resolves to: " + routes.get(___).pattern());  // 👉 replace ___ with 0
    }
}

🧠 Quick Recall

Predict before you peek.

Answer: @PathVariable for the path segment, @RequestParam for the query string.

Answer: @RequestBody deserialises it via a message converter (Jackson).

Common Errors

Pro Tips

📋 Quick Reference

BindAnnotationSource
path segment@PathVariable/users/{id}
query string@RequestParam?q=ann&page=2
JSON body@RequestBodyPOST/PUT payload
header@RequestHeaderNamed HTTP header
fixed status@ResponseStatusConstant code
created201 CreatedSuccessful POST
missing404 Not FoundNo such resource

📚 Resources / Keep Learning

🎉 Lesson Complete!

You can now bind every part of a request — path segments (@PathVariable), query strings (@RequestParam), JSON bodies (@RequestBody) and headers (@RequestHeader) — and return correct HTTP status codes for each operation.

Practice quiz

What does @PathVariable bind?

  • A query string value
  • A segment of the URL path into a method parameter
  • The request body
  • An HTTP header only

Answer: A segment of the URL path into a method parameter. @PathVariable extracts a templated segment of the URL path (e.g. /users/{id}) into a method parameter.

Which annotation binds a query parameter like ?page=2?

  • @PathVariable
  • @RequestBody
  • @RequestParam
  • @ResponseBody

Answer: @RequestParam. @RequestParam binds query-string (or form) parameters such as ?page=2 to method arguments.

What does @RequestBody do?

  • Reads a path segment
  • Deserialises the HTTP request body (e.g. JSON) into an object
  • Sets the status code
  • Adds a header

Answer: Deserialises the HTTP request body (e.g. JSON) into an object. @RequestBody converts the incoming request body (typically JSON) into a Java object using a message converter.

For the route /users/{id}, how is id captured?

  • @RequestParam Long id
  • @RequestBody Long id
  • @PathVariable Long id
  • @RequestHeader Long id

Answer: @PathVariable Long id. A path template segment {id} is bound with @PathVariable Long id.

How do you make a @RequestParam optional with a default?

  • @RequestParam(required=false) or defaultValue=...
  • It cannot be optional
  • Use @PathVariable
  • Use @RequestBody

Answer: @RequestParam(required=false) or defaultValue=.... @RequestParam supports required=false and defaultValue to make a query parameter optional.

Which status code conventionally means a created resource?

  • 200 OK
  • 201 Created
  • 404 Not Found
  • 500 Internal Server Error

Answer: 201 Created. 201 Created indicates a new resource was successfully created, typically returned from a POST.

Which annotation sets a fixed status on a handler without ResponseEntity?

  • @Scope
  • @ResponseStatus
  • @Qualifier
  • @Primary

Answer: @ResponseStatus. @ResponseStatus(HttpStatus.CREATED) declares the status code a handler returns on success.

A request for a missing resource should typically return...

  • 200 OK
  • 201 Created
  • 404 Not Found
  • 301 Moved

Answer: 404 Not Found. 404 Not Found signals the requested resource does not exist.

For a JSON POST body mapped to an object, you use...

  • @PathVariable
  • @RequestParam
  • @RequestBody
  • @RequestHeader

Answer: @RequestBody. @RequestBody binds and deserialises the JSON request body into your DTO/record.

What does @RequestHeader bind?

  • A URL path segment
  • A named HTTP request header value
  • The response body
  • A cookie only

Answer: A named HTTP request header value. @RequestHeader binds the value of a named HTTP request header into a method parameter.

Continue this course

Frequently asked questions

What is the difference between @PathVariable and @RequestParam?

They bind different parts of the URL. @PathVariable extracts a templated segment from the path itself: for the mapping /users/{id}, a request to /users/42 binds id = 42. @RequestParam binds values from the query string (or form data): for /users/search?q=ann&page=2 it binds q = "ann" and page = 2. Use @PathVariable to identify a specific resource (a path is part of the resource's identity), and @RequestParam for optional modifiers like filtering, sorting and pagination. @RequestParam also supports required=false and defaultValue to make a parameter optional, whereas a path variable is part of the route and generally required.

How does @RequestBody turn JSON into an object?

When a request carries a body — typically JSON on a POST or PUT — @RequestBody tells Spring to deserialise that body into the annotated method parameter using an HttpMessageConverter. With spring-boot-starter-web on the classpath, the default converter is Jackson, which maps JSON fields onto your record or POJO properties. So a POST to /users with body {"name":"Ada","email":"ada@x.com"} and a parameter @RequestBody CreateUser input gives you a fully populated CreateUser. It is the mirror image of how returned objects are serialised to JSON, so the same Jackson configuration governs both directions.

How should I choose HTTP status codes for my endpoints?

Follow REST conventions so clients can react correctly. 200 OK for a successful GET or an update that returns a body; 201 Created after successfully creating a resource with POST (often with a Location header); 204 No Content for a successful operation with no body, such as a DELETE; 400 Bad Request for invalid input; 404 Not Found when the requested resource does not exist; and 500 Internal Server Error for unexpected failures. In Spring you set these with ResponseEntity for per-response control, or with @ResponseStatus on the handler (or an exception) for a fixed success or error status. Meaningful status codes are part of a well-designed API contract.

How do I make a query parameter optional?

@RequestParam parameters are required by default, so a missing one yields a 400 Bad Request. To make one optional you have two options: set required = false, in which case the argument is null (or empty Optional) when absent; or supply a defaultValue, such as @RequestParam(defaultValue = "1") int page, which substitutes that value when the parameter is missing and is the cleanest approach for things like pagination. Using defaultValue keeps your handler logic simple because you never have to null-check the value. Path variables, by contrast, are part of the route and are effectively always required unless you define separate mappings.

When should I use @ResponseStatus versus ResponseEntity?

Both set the HTTP status, but at different levels. @ResponseStatus is a declarative annotation placed on a handler method (or on a custom exception class) that fixes the status for the successful (or error) case — for example @ResponseStatus(HttpStatus.CREATED) on a create method. It's concise when the status is always the same. ResponseEntity is programmatic: you decide the status, headers and body at runtime, which is essential when the same handler may return different statuses (200 if found, 404 if not) or needs to set headers like Location. Rule of thumb: use @ResponseStatus for a constant status, ResponseEntity when the response varies per request.