This episode teaches Spring Web MVC for building REST APIs: @RestController and @RequestMapping, how to capture path variables, request params, request bodies, and headers, and how to use ResponseEntity with status codes and JSON serialization.

In episode 3 you created your first endpoint that returned text. Now we build the real foundation: a REST API that accepts input from various sources, returns data in JSON format, and provides correct status codes.
Spring Web MVC is the module that handles all HTTP requests. In this episode you'll master @RestController, the various ways to capture request parameters, and ResponseEntity for full control over responses — skills used in almost every backend application.
The @RestController annotation combines @Controller with @ResponseBody: every value returned by a method is automatically serialized into JSON or XML. To map URLs, use @RequestMapping — either at the class level for a prefix, or at the method level together with @GetMapping, @PostMapping, @PutMapping, and @DeleteMapping.
@RestController
@RequestMapping("/api/items")
public class ItemController {
@GetMapping
public List<Item> listItems() {
return List.of(new Item(1L, "Laptop"));
}
}The class above handles all GET /api/items requests. The Item objects returned will be serialized into JSON by Jackson automatically — without any extra configuration.
Data from the URL can be captured with two annotations. Path variables capture segments inside the path, while request params capture values from the query string:
@GetMapping("/{id}")
public Item getItem(@PathVariable Long id) {
return itemService.findById(id);
}
@GetMapping("/search")
public List<Item> search(@RequestParam String q,
@RequestParam(defaultValue = "10") int limit) {
return itemService.search(q, limit);
}For GET /api/items/search?q=laptop&limit=5, the value of q becomes laptop and limit becomes 5. Parameters that aren't required are given a defaultValue.
To create or update data, send a JSON payload in the request body and capture it with @RequestBody. Read a specific header with @RequestHeader:
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Item createItem(@RequestBody ItemRequest request,
@RequestHeader("X-Client-Id") String clientId) {
return itemService.create(request, clientId);
}The @ResponseStatus(HttpStatus.CREATED) annotation sets status code 201 for successful responses. Jackson maps the JSON fields in the body to the fields of the ItemRequest class automatically — validation details will be covered in episode 7.
Sometimes you need to control the status code, headers, and body at the same time. That's where ResponseEntity comes in. An example endpoint that looks up data and returns 404 if it's not found:
@GetMapping("/{id}")
public ResponseEntity<Item> getItem(@PathVariable Long id) {
return itemService.findById(id)
.map(item -> ResponseEntity.ok(item))
.orElse(ResponseEntity.notFound().build());
}This style makes your API follow HTTP conventions correctly: 200 OK for success, 404 Not Found for missing data, and so on. Consistent status codes are critical so the API can be consumed properly by clients.
Jackson is Spring Boot's default serializer. You can control the shape of the JSON emitted via annotations on fields, for example hiding sensitive fields:
public class Item {
private Long id;
private String name;
@JsonIgnore
private String internalNote;
}The internalNote field with @JsonIgnore won't appear in the JSON — useful for internal fields you don't want exposed to clients. For field naming, Spring Boot uses camelCase by default; the spring.jackson configuration allows further customization.
All the endpoints above can be tested directly from the terminal:
curl http://localhost:8080/api/items
curl -X POST http://localhost:8080/api/items \
-H "Content-Type: application/json" \
-H "X-Client-Id: web" \
-d '{"name":"Laptop"}'The curl -X POST http://localhost:8080/api/items -H "Content-Type: application/json" -d '{"name":"Laptop"}' command sends a JSON payload and triggers the createItem endpoint. Observe the JSON response returned — that's the result of Jackson serialization.
Happens when a JSON request body isn't accompanied by the Content-Type: application/json header. Always include this header when sending data.
Happens when a parameter value doesn't match the type — for example, sending text to a Long parameter. Check the parameter data type and make sure the value is valid. For friendly error messages, we'll build global exception handling in episode 7.
Episode 5 equipped you with the ability to build REST APIs using Spring Web MVC: @RestController and @RequestMapping, capturing path variables, request params, request bodies, and headers, controlling status codes with ResponseEntity, and understanding JSON serialization by Jackson.
Key takeaways:
@RestController automatically serializes method return values into JSON.@PathVariable, @RequestParam, @RequestBody, and @RequestHeader capture input from the URL and body.ResponseEntity gives full control over status code, headers, and body.@JsonIgnore to hide fields.Content-Type: application/json when sending payloads.curl to make sure the status code and JSON shape are correct.In the next episode, episode 6, we'll discuss data access and repositories — Spring Data JPA with @Entity and CrudRepository, automatic query methods, custom queries with @Query, datasource configuration, and the in-memory H2 database for testing. Your API will start storing real data.