REST APIs
REST APIs are how modern apps talk to each other. By the end of this lesson you'll build a real CRUD API with Spring Boot — mapping HTTP verbs to methods, returning correct status codes, validating input, and handling errors cleanly. This is one of the most marketable Java skills you can have.
Learn REST APIs in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A REST API exposes your data as resources living at URLs — /api/users is the whole collection, /api/users/42 is one user. You act on a resource with an HTTP verb , and the server replies with a status code plus (usually) JSON.
💡 Analogy: A REST API is like a restaurant. Each endpoint is a dish on the menu (a resource). You use different verbs to interact — GET (read the menu), POST (place an order), PUT (change your order), DELETE (cancel it). The waiter (the server) answers with a status code : 200 (here's your food), 201 (order created), 404 (we don't serve that), 500 (the kitchen caught fire).
The golden rule: the URL names the thing, the verb names the action . So /api/getUser?id=42 is wrong — the verb is hiding in the URL. The RESTful version is GET /api/users/42 .
Mark a class @RestController and Spring treats its methods as web handlers that return JSON. Add @RequestMapping("/api/users") to set a shared URL prefix, then map each verb to a method: @GetMapping , @PostMapping , @PutMapping , @DeleteMapping .
Three annotations pull data out of the request: @PathVariable grabs the 42 from /api/users/42 , @RequestParam reads query parameters after the ? , and @RequestBody parses the JSON body of a POST/PUT into a Java object.
Wrap the return value in ResponseEntity<T> when the status code varies . Returning a user gives 200 OK ; ResponseEntity.status(HttpStatus.CREATED) gives 201 after a create; .notFound().build() gives 404 ; .noContent().build() gives 204 after a delete.
Finish the three blanks: declare the path variable in the mapping, bind it from the URL, and return 200 OK with the body. The expected requests and responses are in the comments so you can check yourself.
A controller's only job is HTTP — read the request, call something, shape the response. The actual work (rules, calculations, talking to the database) belongs in a @Service class. Spring creates the service and passes it into the controller via constructor injection , so you never new it yourself. Thin controller + fat service is the pattern that keeps real apps testable.
Just as important: never return your database entity directly . Entities often carry sensitive fields — a password hash, internal flags — and serializing the whole thing leaks them to every client. Instead, map the entity to a DTO (Data Transfer Object): a small record holding only the fields the client is allowed to see.
Never trust client input. Put Bean Validation annotations on a request record to describe what "valid" means, then add @Valid to the @RequestBody parameter. Spring checks the rules before your method runs and automatically rejects bad input with 400 Bad Request .
@NotBlank — must not be null or empty/whitespace
@Size(min = 2, max = 50) — string length constraints
@Min(0) @Max(150) — numeric range (inclusive)
For everything else, centralize error handling in one @ControllerAdvice class. Each @ExceptionHandler method catches an exception type and turns it into a clean HTTP response — a validation failure into 400 , a missing resource into 404 — so every endpoint reports errors in the same JSON shape and your controllers stay focused on the happy path.
Fill the three blanks: the annotation that handles POST, the one that reads the JSON body, and the status that means "created". The expected request and response are in the comments.
Now write a small CRUD controller yourself from an outline. Build POST (201), GET-by-id (200 or 404), and DELETE (204) for a Note resource, using @RequestBody , @PathVariable , and ResponseEntity with the correct status codes. The expected requests and responses are in the comments.
Great work! You can now build a production-quality REST API with Spring Boot: map verbs with @GetMapping / @PostMapping / @PutMapping / @DeleteMapping , read input with @PathVariable / @RequestParam / @RequestBody , return correct status codes with ResponseEntity , keep logic in a @Service , hide sensitive fields behind DTOs, validate with @Valid , and handle errors globally with @ControllerAdvice .
Next up: JSON & XML — the serialization formats your API uses to turn objects into the data clients receive.
Practice quiz
What is the RESTful URL for getting user #42?
- GET /api/getUser?id=42
- POST /api/users/42
- GET /api/users/42
- GET /api/user/get/42
Answer: GET /api/users/42. The URL names the resource and the verb names the action: GET /api/users/42. Verbs do not belong in the path.
Which annotation makes a class return JSON from every method automatically?
- @RestController
- @Controller
- @Service
- @Component
Answer: @RestController. @RestController is @Controller + @ResponseBody, so each method serializes its return value to JSON.
Which annotation reads the JSON payload of a POST request into a Java object?
- @PathVariable
- @RequestParam
- @ModelAttribute
- @RequestBody
Answer: @RequestBody. @RequestBody deserializes the JSON body of a POST/PUT into your record or class.
Which annotation pulls the 42 out of /api/users/42?
- @RequestParam
- @PathVariable
- @RequestBody
- @RequestHeader
Answer: @PathVariable. @PathVariable binds a value that is part of the URL path. @RequestParam reads query parameters after the '?'.
What status code should a successful POST that creates a resource return?
- 201 Created
- 200 OK
- 204 No Content
- 404 Not Found
Answer: 201 Created. Creating a new resource should return 201 Created, not a plain 200.
What status code is correct for a successful DELETE with no body to return?
- 200 OK
- 201 Created
- 204 No Content
- 400 Bad Request
Answer: 204 No Content. 204 No Content signals success with nothing to return — the right code after a delete.
Why return ResponseEntity<T> instead of the object directly?
- It is faster
- It lets you set the status code, headers, and whether there's a body
- It is required by Spring
- It automatically validates input
Answer: It lets you set the status code, headers, and whether there's a body. Returning the object always sends 200. ResponseEntity lets the status vary (201, 204, 404) and add headers.
Why map a database entity to a DTO before returning it?
- DTOs are faster to serialize
- Spring requires DTOs
- To make the entity immutable
- To avoid leaking sensitive fields like a password hash to clients
Answer: To avoid leaking sensitive fields like a password hash to clients. Returning the entity serializes every field. A DTO exposes only the fields the client is allowed to see.
How do you make Spring validate a request body before your method runs?
- Call validate() manually
- Add @Valid to the @RequestBody parameter (with Bean Validation annotations on the record)
- Wrap it in a try/catch
- Use @Controller instead of @RestController
Answer: Add @Valid to the @RequestBody parameter (with Bean Validation annotations on the record). @Valid on the @RequestBody triggers the Bean Validation rules (@NotBlank, @Email, @Min...); Spring returns 400 on failure.
What is @ControllerAdvice (or @RestControllerAdvice) used for?
- Injecting services
- Mapping URLs to methods
- One global place to turn exceptions into consistent HTTP error responses
- Defining database entities
Answer: One global place to turn exceptions into consistent HTTP error responses. @ControllerAdvice with @ExceptionHandler methods centralizes error handling so every endpoint returns errors in the same shape.