What is a Data Transfer Object(DTO)? Why is it used?
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);
}Problems:
- Password exposed
- Salary exposed unintentionally
- Internal audit fields exposed
- Tight coupling between the DB model and the API
- Future DB changes break API contracts
- Lazy loading serialization issues
- 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:
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
@Positive4. 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.







