24. REST API and @RestController
REST APIs manipulate resources via HTTP methods (`GET` / `POST` / `PUT` / `DELETE`) and URLs. Spring Boot makes this trivial with `@RestController` and the various mapping annotations.
What you'll learn
- 1Know the difference between `@RestController` and `@Controller`
- 2Know what `@GetMapping` / `@PostMapping` / `@PutMapping` / `@DeleteMapping` do
- 3Use `@PathVariable` and `@RequestParam` for input
- 4Bind JSON to objects with `@RequestBody`
- 5Return status codes with `ResponseEntity`
Overview
REST APIs manipulate resources via HTTP methods (`GET` / `POST` / `PUT` / `DELETE`) and URLs. Spring Boot makes this trivial with `@RestController` and the various mapping annotations.
Core Concepts
1) `@RestController`
Shortcut for `@Controller` + `@ResponseBody`. Whatever you return is serialized to JSON (via Jackson) and sent as the response body.
2) HTTP method annotations
| Annotation | Method |
|---|---|
| `@GetMapping` | GET |
| `@PostMapping` | POST |
| `@PutMapping` | PUT |
| `@DeleteMapping` | DELETE |
| `@PatchMapping` | PATCH |
3) Path / query parameters
@GetMapping("/users/{id}")
public User get(@PathVariable Long id) { ... }
@GetMapping("/search")
public List<User> search(@RequestParam String q, @RequestParam(defaultValue = "10") int limit) { ... }4) `@RequestBody` (JSON in)
record CreateUserRequest(String name, int age) {}
@PostMapping("/users")
public User create(@RequestBody CreateUserRequest req) { ... }5) `ResponseEntity` (custom status)
@PostMapping("/users")
public ResponseEntity<User> create(@RequestBody CreateUserRequest req) {
User saved = service.save(req);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}Examples
Example 1 β `UserController.java`
package com.example.demo;
import java.util.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final Map<Long, String> users = new LinkedHashMap<>();
private long seq = 0;
record CreateUserRequest(String name) {}
@GetMapping
public Collection<String> all() { return users.values(); }
@GetMapping("/{id}")
public ResponseEntity<String> get(@PathVariable Long id) {
String name = users.get(id);
return name == null
? ResponseEntity.notFound().build()
: ResponseEntity.ok(name);
}
@PostMapping
public ResponseEntity<Map<String, Object>> create(@RequestBody CreateUserRequest req) {
long id = ++seq;
users.put(id, req.name());
return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("id", id, "name", req.name()));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return users.remove(id) != null ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build();
}
}Example 2 β curl
$ curl -X POST localhost:8080/users -H 'Content-Type: application/json' -d '{"name":"Jisoo"}'
{"id":1,"name":"Jisoo"}
$ curl localhost:8080/users
["Jisoo"]
$ curl localhost:8080/users/1
Jisoo
$ curl -X DELETE localhost:8080/users/1
Example 3 β `@RequestParam` with default
@GetMapping("/search")
public List<String> search(@RequestParam String q,
@RequestParam(defaultValue = "10") int limit) {
return users.values().stream()
.filter(n -> n.contains(q))
.limit(limit)
.toList();
}Example 4 β Validation
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
record CreateUserRequest(@NotBlank String name, @Min(0) int age) {}
@PostMapping("/users")
public User create(@Valid @RequestBody CreateUserRequest req) { ... }Add `spring-boot-starter-validation` to use Bean Validation annotations.
Common Mistakes
- Returning a domain entity directly β leaks internal fields; use a DTO
- Forgetting `Content-Type: application/json` on POST/PUT requests
- Mixing `@RequestParam` and `@PathVariable` and getting confused
- Returning 200 OK on a failed create β use `201 Created` with `ResponseEntity`
- Skipping validation and trusting client JSON blindly
Summary
- `@RestController` makes a class a JSON API
- Map HTTP methods with `@GetMapping` / `@PostMapping` / etc.
- Use `ResponseEntity` for proper status codes
Practice
# Practice - 24. REST API
## Exercise 1 β `BookController`
- GET `/books` β list
- POST `/books` β create from JSON `{"title":"..."}`
- DELETE `/books/{id}` β remove
## Exercise 2 β Status codes
- Create returns 201 with the new resource.
- Delete returns 204; missing id returns 404.
## Solutions After trying it yourself, compare with [`answer/`](./answer/).
Solution code (homework/answer/)
answer/BookController.java
package com.example.demo;
import java.util.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/books")
public class BookController {
private final Map<Long, String> books = new LinkedHashMap<>();
private long seq = 0;
record CreateBookRequest(String title) {}
@GetMapping
public Collection<Map<String, Object>> all() {
return books.entrySet().stream()
.map(e -> Map.<String, Object>of("id", e.getKey(), "title", e.getValue()))
.toList();
}
@PostMapping
public ResponseEntity<Map<String, Object>> create(@RequestBody CreateBookRequest req) {
long id = ++seq;
books.put(id, req.title());
return ResponseEntity.status(HttpStatus.CREATED)
.body(Map.of("id", id, "title", req.title()));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return books.remove(id) != null
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
}
Try It Yourself
mvn spring-boot:run
curl -X POST localhost:8080/books -H 'Content-Type: application/json' -d '{"title":"Effective Java"}'Next Lecture
[25_Service_Layer](../25_μλΉμ€μ_λ μ΄μ΄/) β split logic into Controller / Service / Repository.
All lecture materials and example code are openly available on GitHub.
View on GitHub β