Entity-DTO Mapping
Mappers convert between entities and DTOs. This keeps your layers decoupled.
Manual Mapper (Simple and Explicit)
@Component
public class ProductMapper {
public ProductDto toDto(Product product) {
ProductDto dto = new ProductDto();
dto.setId(product.getId());
dto.setName(product.getName());
dto.setDescription(product.getDescription());
dto.setPrice(product.getPrice());
dto.setCategoryName(product.getCategory().getName());
dto.setStockQuantity(product.getStockQuantity());
dto.setActive(product.getActive());
dto.setCreatedAt(product.getCreatedAt());
return dto;
}
public Product toEntity(CreateProductRequest request) {
Product product = new Product();
product.setName(request.getName());
product.setDescription(request.getDescription());
product.setPrice(request.getPrice());
product.setStockQuantity(request.getStockQuantity());
return product;
}
public void updateEntity(UpdateProductRequest request, Product product) {
product.setName(request.getName());
product.setDescription(request.getDescription());
product.setPrice(request.getPrice());
}
}
Usage in Service
@Service
public class ProductService {
private final ProductRepository productRepository;
private final ProductMapper productMapper;
public ProductService(ProductRepository productRepository, ProductMapper productMapper) {
this.productRepository = productRepository;
this.productMapper = productMapper;
}
public ProductDto getProduct(Long id) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product not found"));
return productMapper.toDto(product);
}
@Transactional
public ProductDto createProduct(CreateProductRequest request) {
Product product = productMapper.toEntity(request);
// Set relationships
Category category = categoryRepository.findById(request.getCategoryId())
.orElseThrow(() -> new ResourceNotFoundException("Category not found"));
product.setCategory(category);
Product saved = productRepository.save(product);
return productMapper.toDto(saved);
}
}
MapStruct (Compile-Time Code Generation)
@Mapper(componentModel = "spring")
public interface ProductMapper {
@Mapping(source = "category.name", target = "categoryName")
@Mapping(target = "createdAt", ignore = true)
ProductDto toDto(Product product);
@Mapping(target = "id", ignore = true)
@Mapping(target = "category", ignore = true)
Product toEntity(CreateProductRequest request);
@Mapping(target = "id", ignore = true)
@Mapping(target = "category", ignore = true)
void updateEntity(UpdateProductRequest request, @MappingTarget Product product);
}
MapStruct generates the mapping code at compile time — no reflection overhead.
When to Use Each Approach
| Approach | Pros | Cons |
|---|---|---|
| Manual | Full control, easy to debug | Verbose, error-prone for many fields |
| MapStruct | Fast, type-safe, compile-time | Extra dependency, learning curve |
| ModelMapper | Convention-based, minimal code | Hard to debug, reflection overhead |
Best Practices
Key Principles
- Follow SOLID principles
- Write clean, readable code
- Test thoroughly
- Document decisions
- Monitor in production
Implementation
- Start simple, refactor as needed
- Use established patterns
- Consider trade-offs
- Review with peers
Continuous Improvement
- Learn from incidents
- Update documentation
- Share knowledge
- Mentor others
Key Points
- Understanding Mapper is essential for production systems
- Always consider scalability and maintainability
- Test thoroughly before deploying to production
- Monitor performance and set up alerting
Common Patterns
- Validation: Always validate input at the boundary
- Error Handling: Use structured error responses
- Logging: Log key events for debugging
- Testing: Unit, integration, and load tests
- Documentation: Keep docs updated with code changes
Practice Problems
Design and implement a solution for Mapper in a backend system. Consider scalability, error handling, and production readiness.
Solution
// Mapper implementation
// Key aspects: validation, error handling, logging, testing
public class Mapper {
// Production-ready implementation
}Identify and handle edge cases for Mapper. What happens under high load, with invalid input, or during failures?
Solution
// Edge case handling:
// 1. Null/empty input -> validation
// 2. High load -> rate limiting, queuing
// 3. Failures -> retries, circuit breaker
// 4. Concurrent access -> locks, idempotencyWrite a testing strategy for Mapper. Include unit tests, integration tests, and performance tests.
Solution
// Test plan:
// - Unit: 80% coverage target
// - Integration: API contracts
// - Performance: latency, throughput
// - Chaos: failure injectionQuiz
1. What is the advantage of MapStruct over manual mapping?
2. When should you use manual mapping instead of MapStruct?
3. What is the primary purpose of Mapper?
4. What is a common mistake when implementing Mapper?
Flashcards
Question
What does a mapper do?
Click to reveal answer
Answer
Converts between entities and DTOs to keep layers decoupled
Question
MapStruct vs manual mapping?
Click to reveal answer
Answer
MapStruct: compile-time, fast, type-safe. Manual: full control, simple cases
Question
What is Mapper?
Click to reveal answer
Answer
Mapper is a key concept in backend development.
Question
When to use Mapper?
Click to reveal answer
Answer
Use Mapper when building production systems that require reliability, scalability, and maintainability.
Question
Mapper best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1.Mappers convert between entities and DTOs
- 2.Manual mapping gives full control but is verbose
- 3.MapStruct generates mapping code at compile time — fast and type-safe
- 4.Use @Mapping for field name mismatches, @MappingTarget for updates
Interview Tips
- •Know when to use MapStruct vs manual mapping
- •Be ready to write a mapper for a given entity/DTO pair
- •Understand compile-time vs runtime mapping
Cheat Sheet
Mappers
- Manual: Full control, verbose, good for simple cases
- MapStruct: Compile-time, fast, type-safe, good for complex mappings
- @Mapper(componentModel="spring"): Auto-registers as Spring bean
- @Mapping(source, target): Maps field name mismatches