Spring Boot cookbook · Beginner

Map entities to DTOs with MapStruct

Stop hand-writing repetitive mapping code between JPA entities and API records.

Approach

MapStruct generates type-safe mapping code at compile time from an annotated interface. Declare a mapper with componentModel = "spring", map records/entities, customize field names with @Mapping, and use @MappingTarget for updates. Configure unmappedTargetPolicy = ERROR so missing fields fail the build.

Solution

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface ProductMapper {

    @Mapping(target = "price", expression = "java(Money.format(product.getPriceCents(), product.getCurrency()))")
    ProductResponse toResponse(Product product);

    List<ProductResponse> toResponses(List<Product> products);

    @Mapping(target = "id", ignore = true)
    @Mapping(target = "version", ignore = true)
    @Mapping(target = "createdAt", ignore = true)
    void update(UpdateProductRequest request, @MappingTarget Product product);
}

@Service
class ProductService {
    private final ProductRepository repo;
    private final ProductMapper mapper;
    ProductService(ProductRepository repo, ProductMapper mapper) { this.repo = repo; this.mapper = mapper; }

    @Transactional
    public ProductResponse update(long id, UpdateProductRequest request) {
        Product product = repo.findById(id).orElseThrow(() -> new NotFoundException("Product " + id));
        mapper.update(request, product);
        return mapper.toResponse(product);
    }
}

Pitfalls

  • Mapping lazy collections outside a transaction (LazyInitializationException).
  • Silently unmapped fields (set the ERROR policy).
  • Putting business logic in mappers.

Variations

  • Hand-written static from() factories on records for small apps.

Further reading: MapStruct reference guide

Reference: https://mapstruct.org/documentation/stable/reference/html/

More Spring Boot recipes