Skip to content
Spring Boot sb web 4 min read

Request Mapping

Request mapping is how Spring MVC connects an incoming HTTP request to a handler method. The central annotation is @RequestMapping, and Spring Boot adds focused HTTP-verb shortcuts that make controllers far more readable. This page covers the full mapping surface: paths, verbs, content types, and conditional matching.

@RequestMapping basics

@RequestMapping maps a URL pattern (and optionally an HTTP method, headers, params, and media types) to a method. It works at both the class level (a shared prefix) and the method level.

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @RequestMapping(method = RequestMethod.GET)
    public List<Order> all() { ... }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Order one(@PathVariable Long id) { ... }
}

The class-level /api/orders is combined with each method-level path, so one resolves to GET /api/orders/{id}.

HTTP method shortcuts

Writing method = RequestMethod.GET everywhere is noisy. Spring provides composed annotations that bake in the verb. Prefer these in real code.

ShortcutEquivalentHTTP verb
@GetMapping@RequestMapping(method = GET)GET
@PostMapping@RequestMapping(method = POST)POST
@PutMapping@RequestMapping(method = PUT)PUT
@PatchMapping@RequestMapping(method = PATCH)PATCH
@DeleteMapping@RequestMapping(method = DELETE)DELETE

The same controller, rewritten:

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @GetMapping
    public List<Order> all() { ... }

    @GetMapping("/{id}")
    public Order one(@PathVariable Long id) { ... }

    @PostMapping
    public Order create(@RequestBody OrderRequest body) { ... }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) { ... }
}

Tip: Reserve @RequestMapping for the class-level prefix and any handler that genuinely needs to match multiple verbs. Use the verb shortcuts everywhere else.

Class- vs method-level paths

Paths combine. A class-level mapping sets the namespace; method-level mappings extend it.

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

    @GetMapping            // GET /api/v1/users
    public List<User> list() { ... }

    @GetMapping("/{id}/roles")   // GET /api/v1/users/{id}/roles
    public List<Role> roles(@PathVariable Long id) { ... }
}

You can match multiple paths on one method by passing an array:

@GetMapping({"/health", "/healthz"})
public String health() { return "OK"; }

consumes and produces

consumes restricts which request Content-Type a handler accepts; produces declares what it returns and participates in content negotiation against the client’s Accept header.

@PostMapping(
        consumes = MediaType.APPLICATION_JSON_VALUE,
        produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Order> create(@RequestBody OrderRequest body) {
    Order saved = service.create(body);
    return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}

A request with the wrong Content-Type yields 415 Unsupported Media Type; an unsatisfiable Accept header yields 406 Not Acceptable.

Request:

curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: text/plain" \
  -d "oops"

Output:

{
  "timestamp": "2026-06-13T10:14:22.512+00:00",
  "status": 415,
  "error": "Unsupported Media Type",
  "path": "/api/orders"
}

You can also offer multiple representations from one method using produces:

@GetMapping(value = "/{id}",
        produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public Order one(@PathVariable Long id) { ... }

headers and params conditions

@RequestMapping (and the shortcuts) can match only when specific request headers or query params are present, absent, or equal to a value. This is the basis of one common API-versioning strategy.

// Matches only when ?type=summary is present
@GetMapping(value = "/{id}", params = "type=summary")
public OrderSummary summary(@PathVariable Long id) { ... }

// Matches only when ?type=summary is absent
@GetMapping(value = "/{id}", params = "!type")
public Order full(@PathVariable Long id) { ... }

// Header-based selection
@GetMapping(value = "/{id}", headers = "X-API-Version=2")
public OrderV2 v2(@PathVariable Long id) { ... }
ConditionSyntaxMeaning
Param presentparams = "type"?type=... must exist
Param absentparams = "!type"?type must NOT exist
Param equalsparams = "type=summary"exact value match
Header equalsheaders = "X-API-Version=2"request header match

Note: Param and header conditions are narrowing filters, not data binding. To read the value, still use @RequestParam or @RequestHeader — see Path & Query Parameters.

Path patterns and wildcards

Spring uses PathPattern matching. Common tokens:

  • {id} — a single path variable.
  • {*path} — captures the remaining path including slashes.
  • ? matches one char, * matches within a segment, ** matches across segments (use sparingly).
@GetMapping("/files/{*relativePath}")
public Resource serve(@PathVariable String relativePath) { ... }

Pitfalls

  • Do not duplicate the same verb + path across two handlers — Spring throws an ambiguous-mapping error at startup.
  • A trailing-slash mismatch is not auto-tolerated in Spring Boot 3.x (trailing-slash matching was removed); /orders and /orders/ are distinct, so register the canonical form.
  • Overusing ** patterns can create overlapping mappings that are hard to reason about.
Last updated June 13, 2026
Was this helpful?