Building REST APIs with Spring Boot
Design and build a CRUD REST API with proper routes, status codes and validation.
What you’ll learn
- REST resource and route design
- @GetMapping, @PostMapping, @PutMapping, @DeleteMapping
- Returning the right HTTP status codes
- Validating request bodies and handling errors
Designing the routes
REST APIs are organised around resources (nouns) and use HTTP methods (verbs) to act on them. For an employee API:
| Method | Route | Meaning | Success status |
|---|---|---|---|
GET | /api/employees | List employees | 200 OK |
GET | /api/employees/42 | Get one | 200 OK |
POST | /api/employees | Create | 201 Created |
PUT | /api/employees/42 | Replace | 200 OK |
DELETE | /api/employees/42 | Delete | 204 No Content |
Request and response bodies
Use records as DTOs — they're concise and immutable:
public record EmployeeRequest(
@NotBlank String name,
@Email String email,
@Positive long salary) {}
public record EmployeeResponse(long id, String name, String email) {}The validation annotations come from spring-boot-starter-validation.
The controller
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final EmployeeService service;
public EmployeeController(EmployeeService service) {
this.service = service;
}
@GetMapping
public List<EmployeeResponse> list() {
return service.findAll();
}
@GetMapping("/{id}")
public EmployeeResponse get(@PathVariable long id) {
return service.findById(id); // throws EmployeeNotFoundException
}
@PostMapping
public ResponseEntity<EmployeeResponse> create(@Valid @RequestBody EmployeeRequest req) {
EmployeeResponse created = service.create(req);
URI location = URI.create("/api/employees/" + created.id());
return ResponseEntity.created(location).body(created);
}
@PutMapping("/{id}")
public EmployeeResponse update(@PathVariable long id, @Valid @RequestBody EmployeeRequest req) {
return service.update(id, req);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable long id) {
service.delete(id);
}
}Spring converts records to JSON (and back) automatically using Jackson.
Handling errors in one place
Don't catch exceptions in every controller. A @RestControllerAdvice maps exceptions to HTTP responses globally:
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(EmployeeNotFoundException.class)
public ProblemDetail notFound(EmployeeNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail invalid(MethodArgumentNotValidException ex) {
var pd = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
pd.setDetail("Validation failed");
pd.setProperty("errors", ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.toList());
return pd;
}
}ProblemDetail produces the standard RFC 9457 error format, so clients get consistent errors.
Common mistake
Never return your JPA entities directly from controllers. You'll leak internal fields, create lazy-loading surprises and couple your API to your database schema. Map to response DTOs.
⚡ Try it yourself
Implement EmployeeService with an in-memory Map<Long, EmployeeResponse> and an AtomicLong for IDs. Test every endpoint with curl or an HTTP client, including a POST with an invalid email to see your validation error.
Interview questions
QWhat's the difference between @Controller and @RestController?+
@RestController is @Controller plus @ResponseBody on every method, so return values are written directly to the response body (usually as JSON) instead of being resolved as view names.
QPUT vs PATCH?+
PUT replaces the whole resource with the request body and is idempotent. PATCH applies a partial update to specific fields.
Key takeaways
- Resources are nouns; HTTP methods are the verbs.
- Return correct status codes: 201 for create, 204 for delete.
- Validate with
@Validand handle errors centrally with@RestControllerAdvice.
Finished this lesson?
Mark it complete to move forward on your roadmap.