In production systems, bad structure hurts more than bad logic.
I’ve seen Spring Boot applications where:
- Controllers contain business logic
- Entities are returned directly to clients
- Dependencies are hidden and tightly coupled
This article explains how we structure Spring Boot APIs in real projects so they remain readable, testable, and interview-defensible.
This is Part 2 of the Production-Grade Spring Boot API Design series.
What this post covers
- Controller responsibility (API boundary, not logic)
- Why @RestController is more than “return JSON”
- Constructor injection (and why it’s mandatory)
- Spring stereotype annotations and why semantics matter
- Entity vs DTO (and why exposing entities is dangerous)
- Lombok in production systems
- Why ModelMapper keeps layers clean
1. Controller layer (API boundary)
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
}
What @RestController really means
- Registers the class as an HTTP request handler
- Returns JSON (not views)
- Integrated with
DispatcherServlet
This class defines API contracts, not business logic.
2. Constructor Injection (Non‑negotiable in production)
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
Why constructor injection:
- Dependencies are explicit
- Objects are immutable
- Easy to test
- Fails fast during startup
If a dependency is missing, the application should not start.
3. Stereotype annotations (Semantics matter)
| Annotation | Purpose |
|---|---|
@Service |
Business logic |
@Repository |
Data access |
@Controller |
MVC layer |
@RestController |
REST APIs |
All extend @Component, but:
They add meaning, readability, and layer‑specific behavior.
4. Entity vs DTO (Never expose entities)
Entity
- Maps to database tables
- Used only for persistence
DTO
- Used for API communication
- Protects internal structure
Entities change with DB needs. DTOs change with API needs.
5. Lombok (Production sanity)
Without Lombok:
- Missing setters → JSON fields become
null - Missing getters → empty responses
- Huge boilerplate
With Lombok:
- Getters / setters
- Constructors
- Builders
- Clean entities
Lombok is not optional in large Spring Boot codebases.
6. ModelMapper (Layer separation)
- Converts Entity ⇄ DTO
- Keeps controllers clean
- Avoids leaking persistence models
Persistence means:
Data survives beyond application runtime (database storage).
This is Part 2 of a 3-part series on Production-Grade Spring Boot API Design series.
👉 Part 3 covers consistent API responses, BaseResponse, ResponseEntity, and global exception handling.
Top comments (0)