Sitemap

What is a Data Transfer Object(DTO)? Why is it used?

5 min readMay 23, 2026

--

A DTO (Data Transfer Object) is used to transfer only the required data between layers or APIs instead of exposing the full Entity directly.

Get Syed Nazeer’s stories in your inbox

Join Medium for free to get updates from this writer.

With an Employee entity, DTOs are commonly used for:

  • API request/response models
  • Security (hide salary/password/internal fields)
  • Performance optimization
  • Preventing lazy loading issues
  • Versioning APIs
  • Validation handling

Production Grade Use Case

Assume an HR Management System.

Employee Entity (Database Model)

@Entity
@Table(name = "EMPLOYEE_DTO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@Column(unique = true)
private String email;

private String department;

private Double salary;

private String password;

private LocalDate dateOfJoining;

@CreationTimestamp
private LocalDateTime createdAt;

@UpdateTimestamp
private LocalDateTime updatedAt;

}

Problem Without DTO

If we directly return an Entity from the Controller:

@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
Employee emp = employeeService.getEmployeeById(id);
return new ResponseEntity<>(emp, HttpStatus.OK);
}

<OR>

@GetMapping("/{id}")
public Employee getEmployee(@PathVariable Long id) {
return employeeService.getEmployee(id);
}
Press enter or click to view image in full size
Swagger Request
Swagger GET API request
Press enter or click to view image in full size
API response without DTO

Problems:

  1. Password exposed
  2. Salary exposed unintentionally
  3. Internal audit fields exposed
  4. Tight coupling between the DB model and the API
  5. Future DB changes break API contracts
  6. Lazy loading serialization issues
  7. Infinite recursion in relationships

Production DTO Design

Usually, we create separate DTOs for each purpose, as shown below.

1. Employee Request DTO

Used while creating an employee.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EmployeeRequestDTO {

@NotBlank(message = "Name is mandatory")
private String name;

@Email(message = "Email should be valid")
@NotBlank(message = "Email is mandatory")
private String email;

@NotBlank(message = "Department is mandatory")
private String department;

@Positive(message = "Salary must be positive")
private Double salary;

@Size(min = 6, message = "Password must be at least 6 characters long")
private String password;
}

2. Employee Response DTO

Used while sending data to the client.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EmployeeResponseDTO {

private Long id;
private String name;
private String email;
private String department;
private LocalDate dateOfJoining;

}

Notice:

  • Password removed
  • Salary hidden
  • Audit fields hidden

3. Lightweight Summary DTO

Used in list APIs.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EmployeeSummaryDTO {

private Long id;
private String name;
private String email;

}

Repository Layer

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Controller Layer

@RestController
@RequestMapping("/api/v1/employees")
public class EmployeeController {

@Autowired
private IEmployeeService employeeService;

@Operation(summary = "Register a new employee",
description = "Registers a new employee with the provided details.")
@PostMapping
public ResponseEntity<EmployeeResponseDTO> createEmployee(
@Valid @RequestBody EmployeeRequestDTO dto) {
return new ResponseEntity<>(employeeService.createEmployee(dto), HttpStatus.CREATED);
}

@Operation(summary = "Get employee by ID",
description = "Retrieves the details of an employee by their ID.")
@GetMapping("/{id}")
public ResponseEntity<EmployeeResponseDTO> getEmployee(
@PathVariable Long id) {
return new ResponseEntity<>(employeeService.getEmployee(id), HttpStatus.OK);
}

@Operation(summary = "Get all employees",
description = "Retrieves a list of all employees with their summary details.")
@GetMapping
public ResponseEntity<List<EmployeeSummaryDTO>> getAllEmployees() {
return new ResponseEntity<>(employeeService.getAllEmployees(), HttpStatus.OK);
}

}

Service Layer

/**
* Service implementation for managing Employee entities without using ModelMapper.
* This implementation manually maps between entities and DTOs.
* But actual mapping logic is moved to EmployeeMapper class to keep the service clean and focused on business logic.
*/
@Service
public class EmployeeServiceImpl implements IEmployeeService {

@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private EmployeeMapper mapper;

@Override
public EmployeeResponseDTO createEmployee(EmployeeRequestDTO dto) {
// Manual mapping from EmployeeRequestDTO to Employee entity
Employee employee = mapper.mapToEntity(dto);
// Save the employee entity to the database
Employee saved = employeeRepository.save(employee);
// Manual mapping from Employee entity to EmployeeResponseDTO
return mapper.mapToResponseDTO(saved);
}

@Override
public EmployeeResponseDTO getEmployee(Long id) {
// Retrieve the employee entity from the database
Employee employee = employeeRepository.findById(id)
.orElseThrow(() ->
new EmployeeNotFoundException("Employee with id " + id + " not found"));
// Manual mapping from Employee entity to EmployeeResponseDTO
return mapper.mapToResponseDTO(employee);
}

@Override
public List<EmployeeSummaryDTO> getAllEmployees() {
// Retrieve all employee entities from the database
List<Employee> emps = employeeRepository.findAll();
// Manual mapping from List<Employee> to List<EmployeeSummaryDTO>
return mapper.mapToSummaryDTO(emps);
}

}

Mapper

// This class is responsible for manually mapping between Employee entities and various DTOs.
@Component
public class EmployeeMapper {

// Manual mapping method for EmployeeRequestDTO to Employee entity
public Employee mapToEntity(EmployeeRequestDTO dto) {
Employee employee = Employee.builder()
.name(dto.getName())
.email(dto.getEmail())
.department(dto.getDepartment())
.salary(dto.getSalary())
.password((dto.getPassword()))
.dateOfJoining(LocalDate.now())
.build();
return employee;
}

//Manual mapping methods for List<Employee> to List<EmployeeSummaryDTO>
public List<EmployeeSummaryDTO> mapToSummaryDTO(List<Employee> emps) {
return emps
.stream()
.map(emp -> EmployeeSummaryDTO.builder()
.id(emp.getId())
.name(emp.getName())
.email(emp.getEmail())
.build())
.toList();
}

// Manual mapping method for Employee to EmployeeResponseDTO
public EmployeeResponseDTO mapToResponseDTO(Employee emp) {
return EmployeeResponseDTO.builder()
.id(emp.getId())
.name(emp.getName())
.email(emp.getEmail())
.department(emp.getDepartment())
.dateOfJoining(emp.getDateOfJoining())
.build();
}

}

Swagger Results:

Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size

Real Production Benefits

1. Security

Sensitive fields never leave the server and can remain internal.

private String password;
private Double salary;

2. API Stability

DB changes won’t affect clients. Example: added in entity → API still stable.

private String empCode;

3. Validation

The DTO layer handles validation cleanly.

@Email
@NotBlank
@Positive

4. Performance Optimization

Instead of returning large entity graphs:

DTO returns only the needed fields.

5. Prevent LazyInitializationException

Direct entity serialization may trigger:

DTO avoids this.

6. Different Responses for Different APIs

Example:

Production Improvement — Mapper Layer

Instead of manual mapping, production systems often use:

  • MapStruct
  • ModelMapper

Example with MapStruct:

@Mapper(componentModel = "spring")
public interface EmployeeMapper {

Employee toEntity(EmployeeRequestDTO dto);

EmployeeResponseDTO toDTO(Employee employee);
}

Very Important Interview Point

Typical Production Flow

Common DTO Types in Real Projects

Best Practice

Never expose JPA entities directly from REST APIs in production applications.
Always use DTOs between API and persistence layers.

--

--

Written by Syed Nazeer

I'm a Java enthusiast, developing passion for technical content creation.