Expert · Spring Boot basics · topic 54 of 60 · 25 min
Layers: controller, service and repository
Explanation
Why layers?
If one controller method parses JSON, checks business rules, writes SQL and builds the response, it becomes impossible to test or change safely. Layers give each class one kind of job. Rules like "a seat cannot be booked twice" live in exactly one place, the service, whichever way the request arrives (REST, a message queue or a scheduled job).
HTTP request
│
▼
Controller ─ parse input, validate format, map to/from DTOs, pick status codes
│
▼
Service ─ business rules, transactions, calls to other services
│
▼
Repository ─ load and save entities (SQL / JPA)
│
▼
DatabaseA booking flow through the layers
// Controller: HTTP only
@PostMapping("/api/bookings")
public ResponseEntity<BookingDto> book(@Valid @RequestBody BookingRequest req) {
Booking b = bookingService.book(req.trainNo(), req.date(), req.passengerName());
return ResponseEntity.status(HttpStatus.CREATED).body(BookingDto.from(b));
}
// Service: the rules
@Service
public class BookingService {
private final SeatRepository seats;
private final BookingRepository bookings;
public BookingService(SeatRepository seats, BookingRepository bookings) { this.seats = seats; this.bookings = bookings; }
@Transactional
public Booking book(String trainNo, LocalDate date, String passenger) {
if (date.isBefore(LocalDate.now())) throw new IllegalArgumentException("Cannot book a past date");
Seat seat = seats.findFirstFree(trainNo, date)
.orElseThrow(() -> new NoSeatsAvailableException(trainNo, date));
seat.markBooked();
return bookings.save(new Booking(seat, passenger));
}
}
// Repository: data access (Spring Data generates the implementation; next topic)
public interface BookingRepository extends JpaRepository<Booking, Long> { }DTOs versus entities
An entity is a class mapped to a database table (next topic). A DTO (data transfer object), often a record, is the shape of data sent to or from clients. Keeping them separate means you can rename a column without breaking the mobile app, hide internal fields, and accept only the fields a client is allowed to send.
public record BookingDto(long id, String trainNo, String passenger, String seat) {
public static BookingDto from(Booking b) {
return new BookingDto(b.getId(), b.getSeat().getTrainNo(), b.getPassenger(), b.getSeat().getLabel());
}
}Testing each layer
| Layer | How to test | Speed |
|---|---|---|
| Service | Plain JUnit + Mockito mocks for repositories | Milliseconds |
| Controller | @WebMvcTest with the service mocked; check status codes and JSON | Fast (web layer only) |
| Repository | @DataJpaTest against an in-memory or Testcontainers database | Seconds |
| Whole app | @SpringBootTest, sometimes with a real database in Testcontainers | Slowest; fewer of these |
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
Layers: controller, service and repository · 1/6
In this lesson you will organise a backend into layers.
Prefer a video by a teacher? Find free videos on “spring boot layered architecture controller service repository dto” on YouTube ↗
In real life: Adding WhatsApp bookings without duplicating rules
A bus-ticket company first took bookings only through its REST API. Later it added bookings through a WhatsApp chatbot that sends messages to a queue. Because all rules (no past dates, no double booking, fare calculation) were in BookingService, the new message listener simply called the same service method.
@Component
public class WhatsappBookingListener {
private final BookingService bookingService;
public WhatsappBookingListener(BookingService s) { this.bookingService = s; }
public void onMessage(BookingMessage m) {
bookingService.book(m.trainNo(), m.date(), m.passengerName()); // same rules as the REST API
}
}Practice (0/3 done)
Where should the rule "a passenger cannot book a past date" live?
Which is a layering mistake?
Draw (on paper or in a text file) the layers for an "apply leave" feature in an HR system: the controller endpoint and its request/response DTOs, the service method with at least three business rules (enough leave balance, no overlap with existing leave, not in the past), and the repository methods needed. Then write the service method in Java with the repositories mocked in a JUnit test.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- JPA and Spring Data repositoriesSpring Boot basics · same module
- App architecture: layers and repositoriesKotlin and Android App Development · Navigation and app architecture
- What full-stack meansFull-Stack Development · What full-stack development is
- The OSI modelComputer Networking (CCNA level) · How networks work
- ASP.NET Core: your first Web APIC# and .NET · Web APIs with ASP.NET Core and EF Core
- TCP/IP and the network layersCyber Security: from zero to SOC analyst · Networking for security
Questions people ask about this
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.
What is a DTO?
Answer: An object that carries data across a boundary such as the API
Data transfer object.
Which test annotation loads only the web layer for controller tests?
Answer: @WebMvcTest
@WebMvcTest is a test slice for MVC controllers.
Why keep business rules out of controllers?
Answer: Other entry points (queues, jobs) would have to duplicate them
One place for rules.
What does @Transactional on a service method give you?
Answer: All database changes in the method commit or roll back together
Declarative transactions.
Which layer is optional in Google's recommended architecture?Related
Answer: Domain layer
Add a domain layer when logic is shared or complex.
Which package do JPA annotations come from in Spring Boot 3 and later?Related
Answer: jakarta.persistence
The move to Jakarta EE renamed the packages.
Why return DTOs instead of JPA entities from controllers?Related
Answer: To avoid leaking internal fields and coupling the API to the database model
DTOs give a stable, safe API shape.
From Building a REST API
Which job belongs to the backend?Related
Answer: Checking that a note title is not empty before saving it
The backend applies the rules. The frontend can check too, but the backend must always check, because requests can be faked.
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 more topic in “Spring Boot basics” before its exam: JPA and Spring Data repositories.
- Module 11 exam: Spring Boot basics · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Design patterns, JVM internals and interviews. 60 topics left in the course before the final exam.