Skip to content

Expert · Spring Boot basics · topic 53 of 60 · 30 min

Building a REST API

Explanation

A @RestController maps HTTP requests to Java methods: @GetMapping, @PostMapping, @PutMapping and @DeleteMapping, with @PathVariable, @RequestParam and @RequestBody for inputs. Objects are converted to and from JSON automatically. ResponseEntity sets status codes (201 Created, 404 Not Found), @Valid with jakarta.validation annotations checks input, and @RestControllerAdvice turns exceptions into clean error responses.

A controller

java
@RestController
@RequestMapping("/api/products")
public class ProductController {
    private final ProductService service;
    public ProductController(ProductService service) { this.service = service; }

    @GetMapping                                   // GET /api/products?city=Pune
    public List<ProductDto> list(@RequestParam(required = false) String city) {
        return service.list(city);
    }

    @GetMapping("/{id}")                          // GET /api/products/42
    public ResponseEntity<ProductDto> one(@PathVariable long id) {
        return service.find(id)
                .map(ResponseEntity::ok)           // 200 with JSON body
                .orElse(ResponseEntity.notFound().build());   // 404
    }

    @PostMapping                                  // POST /api/products with a JSON body
    public ResponseEntity<ProductDto> create(@Valid @RequestBody NewProduct body) {
        ProductDto saved = service.create(body);
        return ResponseEntity.created(URI.create("/api/products/" + saved.id())).body(saved);   // 201
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable long id) {
        service.delete(id);
        return ResponseEntity.noContent().build();  // 204
    }
}

Jackson (included with the web starter) converts the returned objects to JSON and the request body JSON into the parameter object. Records work well as request and response types (DTOs, data transfer objects).

Validation

java
import jakarta.validation.constraints.*;

public record NewProduct(
        @NotBlank String name,
        @NotBlank String city,
        @Min(1) long pricePaise,
        @PositiveOrZero int stock) { }
// With @Valid on the parameter, invalid input is rejected with 400 Bad Request before your code runs.
// Needs the spring-boot-starter-validation dependency.

Clean error responses

java
@RestControllerAdvice
public class ApiErrors {
    @ExceptionHandler(ProductNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Map<String, String> notFound(ProductNotFoundException e) {
        return Map.of("error", e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> invalid(MethodArgumentNotValidException e) {
        Map<String, String> errors = new LinkedHashMap<>();
        e.getBindingResult().getFieldErrors().forEach(f -> errors.put(f.getField(), f.getDefaultMessage()));
        return errors;
    }
}
One place maps exceptions to HTTP status codes for the whole API.

Status codes to use

CodeMeaningTypical use
200 OKSuccess with a bodyGET, PUT
201 CreatedA new resource was createdPOST (with a Location header)
204 No ContentSuccess, no bodyDELETE
400 Bad RequestInvalid inputValidation errors
401 / 403Not logged in / not allowedSecurity
404 Not FoundNo such resourceUnknown id
409 ConflictRequest conflicts with current stateDuplicate SKU, no seats left
500 Internal Server ErrorUnexpected failureBugs; never on purpose
Careful: Returning JPA entities directly from controllers is a common mistake: it can leak internal fields (password hashes, internal flags), break on lazy-loaded relationships, and tie your API to your database design. Return DTO records built from the entities. Also do not return 200 with {"error": …} in the body; use the right status code.
Think of it like this: A REST controller is like the reception desk of a hospital: requests arrive at different counters (URLs and methods), the receptionist checks the form is complete (validation), passes the case to the right department (service), and hands back a token with a clear status (201 registered, 404 no such patient).
Tip: Test endpoints quickly with curl, HTTPie or Postman, and document them with springdoc-openapi, which generates a Swagger UI page from your controllers.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Building a REST API · 1/6

REST controllers

In this lesson you will build a REST API with Spring Boot.

Prefer a video by a teacher? Find free videos on “spring boot rest api tutorial restcontroller responseentity validation” on YouTube ↗

In real life: Adding a product from the command line

A developer at a retail chain checks the new products API before handing it to the tablet-app team. One curl command sends JSON to the POST endpoint; the reply shows 201 Created and the Location of the new product, exactly as the controller specified.

bash
curl -X POST http://localhost:8080/api/products \
     -H "Content-Type: application/json" \
     -d '{"name":"Steel bottle","city":"Pune","pricePaise":34900,"stock":12}'
# HTTP/1.1 201 Created
# Location: /api/products/1

Practice (0/3 done)

Task 1 · Question

Which annotation reads the 42 in GET /api/products/42 mapped as "/{id}"?

Task 2 · Code · Javascript

The same controller logic in JavaScript, which runs here: write getProduct(params, products) where params.id arrives as TEXT. Return { status: 200, body: product } when found, { status: 404, body: { error: "Product not found" } } otherwise, and { status: 400, body: { error: "id must be a number" } } when the id is not a whole number.

Task 3 · Practice

In a Spring Boot project, build a /api/expenses REST API backed by an in-memory list: GET all, GET by id (404 if missing), POST with validation (description @NotBlank, amount @Positive) returning 201 with a Location header, and DELETE returning 204. Add a @RestControllerAdvice that returns validation errors as JSON. Test every endpoint with curl or Postman.

Quick quiz

Question 1

1.Which status code should a successful POST that creates a resource return?

Question 2

2.What converts Java objects to JSON in a Spring Boot web app?

Question 3

3.What does @Valid do on a @RequestBody parameter?

Question 4

4.Why return DTOs instead of JPA entities from controllers?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which status code should a successful POST that creates a resource return?

Answer: 201

201 Created, ideally with a Location header.

What converts Java objects to JSON in a Spring Boot web app?

Answer: Jackson

Jackson is included with the web starter.

What does @Valid do on a @RequestBody parameter?

Answer: Runs the jakarta.validation constraints and rejects invalid input with 400

Validation happens before your method body.

Why return DTOs instead of JPA entities from controllers?

Answer: To avoid leaking internal fields and coupling the API to the database model

DTOs give a stable, safe API shape.

Which test annotation loads only the web layer for controller tests?Related

Answer: @WebMvcTest

@WebMvcTest is a test slice for MVC controllers.

From Layers: controller, service and repository

What does "stateless" mean for a REST API?Related

Answer: Each request carries everything needed, such as the token; the server does not remember earlier requests

Stateless means no memory of earlier requests, so every request includes its own authentication and data.

From REST APIs and JSON

Which status is right for a successful DELETE with nothing to return?Related
Which HTTP method should delete a resource?Related

Answer: DELETE

DELETE removes the resource at the URL.

From REST API design and JSON handlers

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Spring Boot basics” before its exam: Layers: controller, service and repository, JPA and Spring Data repositories.
  2. Module 11 exam: Spring Boot basics · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Design patterns, JVM internals and interviews. 60 topics left in the course before the final exam.

← Previous: Creating a Spring Boot app