Unravel โบ Java concepts โบ Design patterns and architecture
DTOs and object mapping (MapStruct, ModelMapper)
A DTO (data transfer object) is a plain class that carries data across a boundary (API, message) and is shaped for that boundary.
At a glance
A shipping label versus the whole warehouse record.
- 1Entities model persistence (relations, lazy collections, internal columns); DTOs model the API (only fields the client needs, flattened, with stable names)Java records make compact DTOs.
- 2MapStruct generates mapping code at compile time from an interface: @Mapper(componentModel = "spring") interface OrderMapper { OrderDto toDto(Order o); }@Mapping(source = "customer.name", target = "customerName") handles differing names and nesting.
- 3ModelMapper and Dozer map by reflection at runtime, which is easier to start but slower and fails at runtime instead of compile time
- 4@MappingTarget updates an existing object (for PATCH-style updates) instead of creating a new one
- Accepting the entity directly in a request body invites mass assignment: a client sends "role": "ADMIN" or "id": 1 and it is saved
- Mapping a lazy collection triggers loading, so map inside the transaction (or fetch what you need) to avoid LazyInitializationException and N+1 queries
- MapStruct is an annotation processor: during compilation it generates an OrderMapperImpl class with plain getter and setter calls, so there is no reflection and no runtime overhead
@Mapper(componentModel = "spring")public interface OrderMapper { @Mapping(source = "customer.name", target = "customerName") @Mapping(target = "lineCount", expression = "java(order.getLines().size())") OrderDto toDto(Order order); void update(UpdateOrderRequest request, @MappingTarget Order order);}- line 3 uses Mapping, the core of DTOs and object mapping (MapStruct, ModelMapper)
- line 4 uses Mapping, the core of DTOs and object mapping (MapStruct, ModelMapper)
- line 7 uses Mapping, the core of DTOs and object mapping (MapStruct, ModelMapper)
Read the full explanation (about 2 min)
A DTO (data transfer object) is a plain class that carries data across a boundary (API, message) and is shaped for that boundary. A mapper copies fields between entities and DTOs so that the internal model and the public contract can change independently.
A mental model
A shipping label versus the whole warehouse record. The customer only needs the label (DTO); the warehouse keeps its internal details private, and a clerk (mapper) copies the right fields across.
How it works
Entities model persistence (relations, lazy collections, internal columns); DTOs model the API (only fields the client needs, flattened, with stable names). Java records make compact DTOs.
MapStruct generates mapping code at compile time from an interface: @Mapper(componentModel = "spring") interface OrderMapper { OrderDto toDto(Order o); }. @Mapping(source = "customer.name", target = "customerName") handles differing names and nesting.
ModelMapper and Dozer map by reflection at runtime, which is easier to start but slower and fails at runtime instead of compile time.
@MappingTarget updates an existing object (for PATCH-style updates) instead of creating a new one.
Going deeper
Accepting the entity directly in a request body invites mass assignment: a client sends "role": "ADMIN" or "id": 1 and it is saved. Separate request DTOs with only the settable fields prevent this.
Mapping a lazy collection triggers loading, so map inside the transaction (or fetch what you need) to avoid LazyInitializationException and N+1 queries.
Do not turn mapping into a business layer. Complex rules belong in a service; mappers should be dull, testable copy code.
Under the hood
MapStruct is an annotation processor: during compilation it generates an OrderMapperImpl class with plain getter and setter calls, so there is no reflection and no runtime overhead. The generated file appears under the build output and can be read for debugging.
Common mistakes
- Binding request JSON directly to an entity.
- Reflection mappers in hot paths.
- Mapping lazy relations outside a transaction.
- Business rules hidden in mapper methods.
How Unravel recognises it
When you paste code, Unravel looks for these signs of dtos and object mapping (mapstruct, modelmapper). Spotting them yourself is a good habit when you read unfamiliar code:
- Annotations:
@Mapping@Mappings@MappingTarget@MapperConfig@AfterMapping@BeforeMapping@InheritInverseConfiguration@BeanMapping@ValueMapping@IterableMapping - Types:
ModelMapperMappersTypeMapMapperFacadeDozerDozerBeanMapperโฆDtoโฆDTOโฆRequestโฆResponseBeanUtils - Method calls:
BeanUtils.copyProperties()
Part of a learning path
- Structuring an enterprise service ยท previous: Layered and hexagonal architecture ยท next: @Transactional
Related concepts
Frequently asked questions
What is DTOs and object mapping (MapStruct, ModelMapper)?
A DTO (data transfer object) is a plain class that carries data across a boundary (API, message) and is shaped for that boundary. A mapper copies fields between entities and DTOs so that the internal model and the public contract can change independently.
What are the most common mistakes with DTOs and object mapping (MapStruct, ModelMapper)?
Binding request JSON directly to an entity. Reflection mappers in hot paths. Mapping lazy relations outside a transaction. Business rules hidden in mapper methods.
How can I see DTOs and object mapping (MapStruct, ModelMapper) in real code?
Paste your own Java into Unravel and it will point out where dtos and object mapping (mapstruct, modelmapper) appears, explain how it works at the detail level you choose, and draw the flow of the code around it.