Capstone: A Complete REST API
Everything comes together: a CRUD REST API with a controller → service → repository → JPA flow, validation , error handling , security , and tests — the anatomy of a production Spring Boot service.
Learn Capstone: A Complete REST API in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
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.
Before You Start
This capstone draws on the whole track: dependency injection , security , testing , and containerization . Skim any you're unsure of first.
What You'll Learn
The Big Idea — A Restaurant's Front of House and Kitchen
💡 Analogy: A REST API runs like a restaurant. The waiter (controller) takes your order, checks it makes sense ( validation ), and never cooks anything themselves. They pass it to the chef (service), who knows the recipes and the rules ( business logic ). The chef fetches ingredients from the pantry (repository / JPA / database). The doorman (security) checks who may enter and which tables they can use. If something's wrong — no such dish — the manager ( @RestControllerAdvice ) sends a polite, consistent message back (a 404), not chaos. And the health inspectors ( tests ) visit regularly to make sure every station does its job.
Clear roles, clean hand-offs, and consistent responses — that's a well-built API.
1️⃣ The Flow — Controller → Service → Repository
Each layer has one job: the controller handles HTTP, the service holds logic, the repository handles data. The plain-Java model below shows the flow — including a missing resource becoming a 404.
2️⃣ Validation and Error Handling
Reject bad input with a 400 before it reaches your logic, and turn missing resources into a 404 . Spring uses @Valid + Bean Validation and @RestControllerAdvice ; the demo hand-rolls the same idea.
3️⃣ The Spring Version — Controller, Service, JPA
Now the real thing: a @RestController delegating to a @Service backed by a Spring Data JpaRepository , with @Valid for validation and @RestControllerAdvice for centralized error handling.
4️⃣ Security and Tests — Completing the Picture
A stateless SecurityFilterChain guards the API, and @WebMvcTest + MockMvc verify the controller — including the 404 path — with the service mocked.
🧠 Quick Recall
Answer: the service layer — controllers stay thin (HTTP) and repositories stay focused on data access.
Answer: 400 Bad Request — triggered by @Valid failing Bean Validation constraints.
Answer: throw a not-found exception and map it with @ExceptionHandler in a @RestControllerAdvice .
🎯 YOUR TURN — A Service Lookup with 404
Complete get(id) so a missing id throws (the controller would map it to 404).
🧩 MINI-CHALLENGE — A Full Create Flow
Validate the request, then persist and return 201 with an id, or return 400 with the errors.
Common Errors
- ❌ Fat controllers. Business logic leaks into the web layer. Fix: push logic into the service.
- ❌ Returning 200 for failures. Clients can't detect errors. Fix: use correct status codes (400/404/409).
- ❌ No @Valid on request bodies. Bad input reaches the service. Fix: validate at the boundary.
- ❌ Leaking entities/stack traces. Exposes internals. Fix: use DTOs and @RestControllerAdvice .
- ❌ Stateful sessions in a token API. Breaks scaling. Fix: set SessionCreationPolicy.STATELESS .
- ❌ Only full-context tests. Slow, hard to diagnose. Fix: favour slice tests, add a few integration tests.
Pro Tips
- 💡 Keep layers honest: controllers never touch repositories directly — always go through the service.
- 💡 Return a Location header with 201 Created so clients can find the new resource.
- 💡 Centralize error responses in one @RestControllerAdvice for a consistent contract.
- 💡 Expose /actuator/health and ship the app as a container to round out a production-ready service.
📋 Quick-Reference Recap — The Whole Track
Concern
Key annotation / idea
Note
wiring
constructor DI, @Service
Decoupled, testable
web layer
@RestController, @GetMapping
HTTP only
data layer
JpaRepository
Persistence
validation
@Valid + constraints
400 on failure
error handling
@RestControllerAdvice
e.g. 404, 409
security
SecurityFilterChain
Stateless + JWT
web tests
@WebMvcTest + MockMvc
Mock the service
data tests
@DataJpaTest
Rolled back
monitoring
Actuator + Micrometer
/health, /metrics
caching
@Cacheable / @CacheEvict
Speed + freshness
deploy
Docker / buildpacks
Env-var config
📚 Resources / Keep Learning
- 📘 Building REST services with Spring — tutorial
- 📗 Web (Servlet) applications — Spring Boot reference
- 📗 Building a RESTful Web Service — Spring guide
❓ Frequently Asked Questions
🎉 Track Complete!
You've assembled a complete REST API: a controller → service → repository → JPA flow with validation, centralized error handling, stateless security, and a layered test strategy — and you know how to monitor, cache, and containerize it. That's the full anatomy of a production Spring Boot service.
Congratulations on finishing the Spring Boot & Enterprise Java track. Keep building — the best way to cement these ideas is to ship a small API of your own.
Practice quiz
In the classic layered architecture, which layer talks to the database?
- Controller
- Service
- Repository
- DTO
Answer: Repository. The repository (data access) layer is responsible for persistence; the service orchestrates and the controller handles HTTP.
What is the controller layer responsible for?
- Persisting entities
- Handling HTTP requests/responses and delegating to the service
- Defining the database schema
- Hashing passwords
Answer: Handling HTTP requests/responses and delegating to the service. Controllers map HTTP to method calls, validate input, and delegate business logic to the service layer.
Where does business logic belong in this design?
- The controller
- The repository
- The service layer
- The entity getters
Answer: The service layer. The service layer holds business rules and coordinates repositories, keeping controllers thin and repositories focused on data.
Which annotation triggers Bean Validation on a request body?
- @Valid
- @Cacheable
- @Transactional
- @Bean
Answer: @Valid. Annotating a @RequestBody parameter with @Valid runs Bean Validation constraints like @NotBlank and @Size.
What is a good way to return a 404 when a resource is missing?
- Return null
- Log and continue
- Return an empty 200
- Throw an exception handled by @ExceptionHandler/@ControllerAdvice mapping to 404
Answer: Throw an exception handled by @ExceptionHandler/@ControllerAdvice mapping to 404. Throw a not-found exception and map it to HTTP 404 via @ExceptionHandler or @ControllerAdvice for consistent error handling.
Which HTTP status best fits a successful resource creation?
- 200 OK
- 201 Created
- 204 No Content
- 400 Bad Request
Answer: 201 Created. 201 Created signals a new resource was created, typically with a Location header pointing to it.
@ControllerAdvice is used to...
- create beans
- configure caching
- centralize cross-cutting exception handling for controllers
- define entities
Answer: centralize cross-cutting exception handling for controllers. @ControllerAdvice (or @RestControllerAdvice) centralizes exception handling and other advice across controllers.
To slice-test only the web layer of this API you would use...
- @DataJpaTest
- @WebMvcTest with MockMvc and @MockBean
- @SpringBootTest only
- no annotation
Answer: @WebMvcTest with MockMvc and @MockBean. @WebMvcTest loads just the controller; MockMvc drives requests and @MockBean stubs the service.
In a secured REST API, a stateless token is typically sent as...
- a query string password
- a hidden form field
- an X-Powered-By header
- Authorization: Bearer <jwt>
Answer: Authorization: Bearer <jwt>. Stateless APIs carry a JWT in the Authorization header as a Bearer token, validated by a security filter.
Why map JPA entities to DTOs at the API boundary?
- To slow responses
- To decouple the API contract from the persistence model and avoid leaking internals
- Because entities can't be serialized
- To skip validation
Answer: To decouple the API contract from the persistence model and avoid leaking internals. DTOs keep the external contract stable and prevent exposing or over-binding internal entity fields.
Continue this course
- Previous: Containerizing a Spring Boot App
- Next: Database Migrations with Flyway