← Back to Java series
🌱
Spring Boot
Spring Boot Β· Prerequisite: previous lecture

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.

Spring BootJavaREST@RestController
Duration
⏱ ~1.5-2 hours
Level
πŸ“Š Intermediate-Advanced
Prerequisite
🎯 Previous lecture or equivalent knowledge
OUTCOME
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

AnnotationMethod
`@GetMapping`GET
`@PostMapping`POST
`@PutMapping`PUT
`@DeleteMapping`DELETE
`@PatchMapping`PATCH

3) Path / query parameters

java
@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)

java
record CreateUserRequest(String name, int age) {}

@PostMapping("/users")
public User create(@RequestBody CreateUserRequest req) { ... }

5) `ResponseEntity` (custom status)

java
@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`

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

bash
$ 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

java
@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

java
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

  1. Returning a domain entity directly β€” leaks internal fields; use a DTO
  2. Forgetting `Content-Type: application/json` on POST/PUT requests
  3. Mixing `@RequestParam` and `@PathVariable` and getting confused
  4. Returning 200 OK on a failed create β€” use `201 Created` with `ResponseEntity`
  5. 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

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

bash
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.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—