DTO in Spring REST — Data Transfer Object Best Practice
Fast recall
Quick revision
Core answer
Understanding API design best practices and the dangers of exposing domain entities directly
Returning JPA @Entity classes directly from controllers, which exposes internal DB structure and causes lazy-loading serialization errors
Mention specific risks: exposing passwords or sensitive fields, triggering N+1 queries from lazy collections, coupling API clients to your database schema changes
Interview-ready guide
Interview answer
For example, CreateUserRequest can contain name, email, and a plaintext password accepted over TLS, while UserResponse contains id, name, and email but never passwordHash. Mapping can be explicit Java code or a tested mapper such as MapStruct. The entity can later gain audit columns without silently changing the response JSON.
DTO in Spring REST — Data Transfer Object Best Practice
Direct Answer
A DTO is a Java value used to carry a request or response shape without exposing the persistence/domain object as the wire contract. It can include only the fields for one use case, carry validation, and keep API evolution separate from entity mapping. DTOs are especially valuable at public, security-sensitive, or independently versioned boundaries; they are a design choice rather than a rule that every tiny internal endpoint needs a distinct class per operation.
Code Example
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
// Input DTO: only fields needed to create a user
@PostMapping
public UserResponse createUser(@RequestBody CreateUserRequest request) {
var user = userService.create(request);
return toResponse(user); // Convert entity to output DTO
}
// Output DTO: exclude sensitive fields, include computed fields
@GetMapping("/{id}")
public UserResponse getUser(@PathVariable Long id) {
var user = userService.findById(id);
return toResponse(user);
}
private UserResponse toResponse(User user) {
return new UserResponse(user.getId(), user.getName(), user.getEmail());
}
}
// Input DTO: what the client sends
record CreateUserRequest(String name, String email, String password) {}
// Output DTO: what the API returns (no password!)
record UserResponse(Long id, String name, String email) {}
Common Mistakes & Pitfalls
- Returning JPA entities directly from controllers: Freshers often do
return userRepository.findById(id).orElseThrow()straight from the controller. This exposes every field in the entity, includingpasswordHash,version,createdBy, and triggers lazy-loading exceptions if Jackson tries to serialize a@OneToManycollection. Always map to a DTO before returning. - Creating one DTO for everything: Having a single
UserDTOused for create, update, list, and detail responses defeats the purpose. Each operation should have its own DTO with exactly the fields needed —CreateUserRequest,UpdateUserRequest,UserSummary,UserDetail. - Manual mapping hell: Writing
new UserResponse(user.getId(), user.getName(), ...)everywhere is tedious. Use a mapping library like MapStruct (compile-time) or ModelMapper (runtime) to automate conversion, or put the mapping logic in a dedicated mapper class. - Not validating input DTOs: Just because you're using a DTO doesn't mean the data is safe — add Bean Validation annotations like @NotNull, @Email, @Size to input DTOs so Spring validates them automatically when paired with @Valid.
Check your understanding
Answer these without looking back at the article.
- What is MapStruct and how does it help with DTO mapping?
- What are the risks of returning JPA entities directly from REST controllers?
- What is the difference between a Request DTO and a Response DTO?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.