What is a DTO (Data Transfer Object)? Complete Guide with Spring Boot

In modern backend systems, especially when working with frameworks like Spring Boot, the concept of DTOs (Data Transfer Objects) plays a critical role in clean architecture, security, and performance. This blog explores what DTOs are, why you need them, and how to effectively use them in Java and Spring Boot applications.

What is a DTO?

A DTO (Data Transfer Object) is a plain Java class that holds data and is used to transfer data between layers (typically controller ↔ service or client ↔ server). It does not contain any business logic.

DTOs are often used in APIs or services to return exactly what is needed — no more, no less. They help in shaping the response payloads and controlling the visibility of internal application structure.

Example DTO:


public class UserDTO {
  private String name;
  private String email;

  // Constructors, getters, setters
}
    

Why Use DTOs?

Here are some of the key reasons for using DTOs in your backend development:

  • Encapsulation: Prevent exposing internal data structures (like JPA entities) directly to the client.
  • Security: Hide sensitive fields (e.g., passwords, roles) from frontend responses.
  • Performance: Reduce the response size by sending only required fields.
  • Version Control: Create different DTO versions for legacy and modern APIs.
  • Decoupling: Detach service and controller layers from the database structure.

DTO vs Entity

In many beginner applications, it's common to use JPA entities directly in the controller. While this might work in small projects, it becomes risky and messy in production-grade systems.

Aspect Entity DTO
Purpose Represents DB table Transfers data between layers
Used in Persistence Layer Service/Controller Layer
Contains Annotations @Entity, @Table Usually none
Serializable Not always Yes (recommended)

Common Use Cases of DTOs

DTOs are used in various scenarios:

  • REST API Responses: Only expose selected data to clients
  • API Versioning: Use UserV1DTO, UserV2DTO, etc.
  • Data Aggregation: Combine data from multiple entities into a single DTO
  • Form Handling: Use DTOs to accept user input and validate it

Security Benefits of DTO

By using DTOs, you can:

  • Prevent exposure of internal fields like passwordHash, roles, or audit logs
  • Sanitize incoming requests by ignoring malicious or irrelevant fields
  • Avoid exposing entity relationships that are not needed externally

Creating and Using DTOs in Spring Boot

In a typical Spring Boot application, DTOs are defined in a separate package like com.example.dto. You’ll then map entities to DTOs in the service layer before returning them to the controller.

Directory structure:


com.example.project
├── controller
├── service
├── model
├── dto
└── repository
    

Step 1: Define DTO Class

UserDTO.java


public class UserDTO {
    private Long id;
    private String name;
    private String email;

    // Constructors, getters, setters
}
    

Step 2: Map Entity to DTO (Manually)

You can convert between entity and DTO manually using a constructor or builder.


public UserDTO mapToDTO(User user) {
    UserDTO dto = new UserDTO();
    dto.setId(user.getId());
    dto.setName(user.getName());
    dto.setEmail(user.getEmail());
    return dto;
}
    

This method works fine for simple projects, but becomes repetitive for larger ones.

Step 3: Using ModelMapper or MapStruct

Option 1: ModelMapper

ModelMapper is a library that helps automatically map fields between objects.


ModelMapper mapper = new ModelMapper();
UserDTO dto = mapper.map(user, UserDTO.class);
    

Add Maven dependency:


<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>3.1.0</version>
</dependency>
    

Option 2: MapStruct (Compile-time)

MapStruct is a compile-time mapping framework that generates efficient mapping code.


@Mapper(componentModel = "spring")
public interface UserMapper {
    UserDTO toDTO(User user);
    User toEntity(UserDTO dto);
}
    

Step 4: Use DTO in Service Layer

In your service layer, map the entity returned by the repository to a DTO before returning it to the controller.


@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public UserDTO getUserById(Long id) {
        User user = userRepository.findById(id)
                     .orElseThrow(() -> new RuntimeException("User not found"));
        return mapToDTO(user); // or use ModelMapper/MapStruct
    }
}
    

Step 5: Use DTO in Controller

Finally, return the DTO from your controller rather than the entity.


@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        UserDTO dto = userService.getUserById(id);
        return ResponseEntity.ok(dto);
    }
}
    

Nested DTOs and Composition

DTOs can contain other DTOs to represent relationships between objects without leaking entity structures. This is especially useful in OneToMany or ManyToOne relationships.

Example: A UserDTO containing a list of AddressDTO


public class UserDTO {
    private Long id;
    private String name;
    private List<AddressDTO> addresses;
}

public class AddressDTO {
    private String street;
    private String city;
}
    

Use Java Streams or mappers to convert nested entities to their corresponding DTOs.

List Mapping with Java Streams

Mapping a list of entities to DTOs is common in REST APIs.


List<UserDTO> dtos = users.stream()
  .map(this::mapToDTO)
  .collect(Collectors.toList());
    

Libraries like ModelMapper or MapStruct can automate this with type-safe configuration.

DTO Validation with @Valid

DTOs are also used for input validation using annotations like @NotNull, @Email, @Size, etc.


public class RegisterUserDTO {
    @NotNull
    @Size(min = 3)
    private String username;

    @Email
    private String email;
}
    

In the controller:


@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody @Valid RegisterUserDTO dto) {
    // validation errors are handled automatically
}
    

Spring Boot will return an automatic 400 Bad Request response if validation fails.

Best Practices for DTO Usage

  • Always separate DTOs from Entities
  • Use descriptive names like UserDTO, UserResponse, or UserRequest
  • Avoid putting logic in DTOs — keep them as pure data carriers
  • Use automated mapping for large projects (MapStruct or ModelMapper)
  • Create versioned DTOs for APIs with evolving contracts
  • Validate incoming DTOs with @Valid and custom error handlers

Summary

DTOs are a powerful tool in modern Java backend development. They help achieve decoupled architecture, protect sensitive data, and simplify validation and testing. In Spring Boot, DTOs enhance your API’s design, usability, and security.

This guide walked through creating DTOs, mapping them manually and automatically, validation, and working with nested objects.

By structuring your application with DTOs, you prepare it for scale, maintainability, and clean API design.

Post a Comment

0 Comments