Spring Boot Tutorial
Request Mapping and Parameters
Every REST endpoint starts with two questions: which requests should reach this method, and how does data from the request get into Java variables? Spring MVC answers both with annotations — @RequestMapping and its shortcuts for routing, and a family of parameter annotations for binding path segments, query strings, headers, cookies and bodies.
This lesson is a complete reference to request mapping in Spring Boot 4: HTTP method shortcuts, URI patterns, path variables, query parameters with defaults and optional values, headers, cookies, request bodies, binding query parameters into objects, content negotiation with consumes/produces, and building responses with ResponseEntity.
Routing Annotations
@RequestMapping on a class sets a common path prefix. On methods, use the shortcuts @GetMapping, @PostMapping, @PutMapping, @PatchMapping and @DeleteMapping. Mappings can also be narrowed by params, headers, consumes (request Content-Type) and produces (response types the client accepts), and in Spring Framework 7 by version (see the API versioning lesson).
URI Patterns
Spring MVC uses PathPattern matching. /files/* matches one path segment, /files/** matches any number of trailing segments, {id} captures a segment, {id:\d+} captures a segment matching a regular expression, and {*path} captures the rest of the path. Trailing slashes are not matched by default: /api/users/ does not match /api/users.
Binding Request Data
Each annotation reads from a different part of the request:
@PathVariable— a segment of the URL path, e.g./courses/{slug}.@RequestParam— a query string or form parameter; supportsdefaultValue,required = false,Optional, and lists (?tag=a&tag=b).@RequestHeader— an HTTP header such asAccept-Languageor a customX-Request-Id.@CookieValue— a cookie value.@RequestBody— the request body, converted from JSON by Jackson.@ModelAttribute(or no annotation) — binds many query parameters into one object or record, ideal for search filters.HttpServletRequest,Principal,Locale,Pageable— injected directly by type.
Type Conversion
Spring converts strings from the request into the declared parameter types: numbers, booleans, enums (by name), UUID, LocalDate (with @DateTimeFormat(iso = ISO.DATE) or the global spring.mvc.format.date property) and more. A conversion failure — /orders/abc for a Long id — produces a 400 Bad Request automatically.
Building Responses with ResponseEntity
Returning an object gives 200 OK with a JSON body. Use ResponseEntity to control status, headers and body: ResponseEntity.created(location).body(dto) for 201, ResponseEntity.noContent().build() for 204, ResponseEntity.ok().eTag(...) for caching. @ResponseStatus on a method sets a fixed status for void methods.
Examples
Path variables, patterns and query parameters
@RestController
@RequestMapping("/api/courses")
public class CourseController {
// GET /api/courses?level=BEGINNER&tag=java&tag=oop&page=0
@GetMapping
public List<String> list(@RequestParam(required = false) Level level,
@RequestParam(defaultValue = "") List<String> tag,
@RequestParam(defaultValue = "0") int page) {
return List.of("level=" + level, "tags=" + tag, "page=" + page);
}
// GET /api/courses/spring-boot
@GetMapping("/{slug}")
public String bySlug(@PathVariable String slug) {
return "course " + slug;
}
// GET /api/courses/42/lessons/7 (numeric ids only)
@GetMapping("/{courseId:\\d+}/lessons/{lessonId:\\d+}")
public String lesson(@PathVariable long courseId, @PathVariable long lessonId) {
return "course " + courseId + " lesson " + lessonId;
}
// GET /api/courses/files/images/2026/logo.png
@GetMapping("/files/{*path}")
public String file(@PathVariable String path) {
return "file path = " + path;
}
}
GET /api/courses?level=BEGINNER&tag=java&tag=oop -> ["level=BEGINNER","tags=[java, oop]","page=0"]
GET /api/courses/spring-boot -> course spring-boot
GET /api/courses/42/lessons/7 -> course 42 lesson 7
GET /api/courses/abc/lessons/7 -> 404 (regex does not match)
GET /api/courses/files/images/2026/logo.png -> file path = /images/2026/logo.png
GET /api/courses?level=EXPERT -> 400 Bad Request (not a Level enum value)
Headers, cookies, dates and binding query parameters into a record
public record OrderSearch(String status,
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
Integer minAmount) {}
@RestController
@RequestMapping("/api/orders")
public class OrderQueryController {
// GET /api/orders/search?status=PAID&from=2026-09-01&to=2026-09-30&minAmount=500
@GetMapping("/search")
public OrderSearch search(OrderSearch criteria) { // bound from query parameters
return criteria;
}
@GetMapping("/whoami")
public Map<String, Object> whoami(@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "X-Request-Id", required = false) String requestId,
@CookieValue(value = "theme", defaultValue = "light") String theme,
Locale locale) {
return Map.of("userAgent", userAgent, "requestId", String.valueOf(requestId),
"theme", theme, "locale", locale.toLanguageTag());
}
}
GET /api/orders/search?status=PAID&from=2026-09-01&to=2026-09-30&minAmount=500
{"status":"PAID","from":"2026-09-01","to":"2026-09-30","minAmount":500}
GET /api/orders/whoami (Cookie: theme=dark, Accept-Language: hi-IN)
{"userAgent":"curl/8.9.1","requestId":"null","theme":"dark","locale":"hi-IN"}
Request bodies, consumes/produces and ResponseEntity
public record CreateNoteRequest(@NotBlank String title, String body) {}
public record NoteDto(Long id, String title, String body) {}
@RestController
@RequestMapping("/api/notes")
public class NoteController {
private final NoteService notes;
public NoteController(NoteService notes) {
this.notes = notes;
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<NoteDto> create(@Valid @RequestBody CreateNoteRequest req,
UriComponentsBuilder uri) {
NoteDto saved = notes.create(req);
URI location = uri.path("/api/notes/{id}").buildAndExpand(saved.id()).toUri();
return ResponseEntity.created(location).body(saved);
}
@GetMapping(value = "/{id}", produces = "text/markdown")
public String asMarkdown(@PathVariable Long id) {
NoteDto n = notes.get(id);
return "# " + n.title() + "\n\n" + n.body();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
notes.delete(id);
return ResponseEntity.noContent().build();
}
}
POST /api/notes Content-Type: application/json {"title":"JPA","body":"Use LAZY"}
-> 201 Created, Location: http://localhost:8080/api/notes/3, {"id":3,"title":"JPA","body":"Use LAZY"}
POST /api/notes Content-Type: text/plain -> 415 Unsupported Media Type
GET /api/notes/3 Accept: text/markdown -> "# JPA\n\nUse LAZY"
DELETE /api/notes/3 -> 204 No Content
Common Mistakes
- Using @RequestParam for data that identifies a resource; resource ids belong in the path (/orders/42), filters in the query string.
- Declaring primitive int for an optional query parameter without a defaultValue, causing a 400 or IllegalStateException when it is missing.
- Forgetting @RequestBody on a POST parameter, so Spring tries to bind query parameters and every field is null.
- Expecting /api/users/ to match /api/users — trailing-slash matching is off by default since Spring Framework 6.
- Returning 200 OK for creation and deletion instead of 201 Created with a Location header and 204 No Content.
Key Points to Remember
- Use @GetMapping/@PostMapping/@PutMapping/@PatchMapping/@DeleteMapping with a class-level @RequestMapping prefix.
- @PathVariable, @RequestParam, @RequestHeader, @CookieValue and @RequestBody read different parts of the request.
- Bind many query parameters into a record for search endpoints.
- consumes and produces restrict mappings by content type; mismatches return 415 or 406.
- ResponseEntity controls status codes and headers such as Location.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.