DEV Community

Pratik280
Pratik280

Posted on

Production-Grade Spring Boot APIs — Part 2: Clean Code Structure, Controllers & DTOs

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)