In modern Java applications — particularly those built on Spring Boot or Jakarta EE — moving data between object models is a daily reality. You have DTOs (Data Transfer Objects) arriving from REST APIs and you need to convert them into Domain Entities for persistence, or vice versa. Writing manual getter-setter copy code for every field is tedious, error-prone, and buries your business logic under a mountain of boilerplate that grows with every new field added to your model.
Enter Dozer — a robust, open-source Java Bean mapper that recursively copies data from one object to another using reflection. Dozer has been a production staple for over a decade. While compile-time alternatives like MapStruct have gained popularity for performance-critical applications, Dozer remains a compelling choice for its runtime flexibility, zero build-plugin setup, and its ability to handle complex mapping scenarios without annotation proliferation on your model classes.
In this guide, we will cover practical Dozer usage from basic field mapping through nested objects, custom converters, and Spring integration — then benchmark Dozer against MapStruct so you can make an informed architectural decision for your next project.
Why Use a Bean Mapper at All?
A typical layered application maintains at least three object representations of the same data: an API DTO (shaped for the consumer), a domain entity (shaped for business logic), and a persistence entity (shaped for the database). Manually copying between these introduces several risks: missed fields when a model is updated, inconsistent null handling, and tight coupling between layers. A bean mapper like Dozer encapsulates all field mapping rules in one place, so adding a new field to the DTO and the entity automatically propagates the mapping without touching service code.
1. Getting Started: Maven Dependency
Add dozer-core to your pom.xml. No annotation processors or build plugins are required.
<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-core</artifactId>
<version>6.5.0</version>
</dependency>
Instantiate the Mapper once — in a Spring application, define it as a singleton @Bean. Creating a new mapper per request is a serious performance antipattern because the mapper must parse all mapping metadata on each construction.
// Spring @Configuration class
@Bean
public Mapper dozerMapper() {
return DozerBeanMapperBuilder.buildDefault();
}
2. Basic Mapping: Identical and Mismatched Fields
When field names match between source and destination, Dozer maps them automatically via convention. For mismatched names, use the fluent BeanMappingBuilder API.
// Source
public class UserDTO {
private long id;
private String username;
private String email; // name mismatch with entity
// getters and setters
}
// Destination
public class UserEntity {
private long id;
private String username;
private String emailAddress; // different name
// getters and setters
}
BeanMappingBuilder builder = new BeanMappingBuilder() {
@Override
protected void configure() {
mapping(UserDTO.class, UserEntity.class)
.fields("email", "emailAddress");
}
};
Mapper mapper = DozerBeanMapperBuilder.create()
.withMappingBuilder(builder)
.build();
UserDTO dto = new UserDTO();
dto.setId(1L);
dto.setUsername("ankurm");
dto.setEmail("[email protected]");
UserEntity entity = mapper.map(dto, UserEntity.class);
System.out.println("ID: " + entity.getId()); // ID: 1
System.out.println("User: " + entity.getUsername()); // User: ankurm
System.out.println("Email: " + entity.getEmailAddress()); // Email: [email protected]
The id and username fields are mapped automatically. Only the mismatched email → emailAddress pair requires explicit configuration.
3. Nested Object Mapping
Dozer automatically traverses and maps nested object graphs — it instantiates the destination type and recursively maps all matching fields, without any additional configuration.
public class UserDTO { private String username; private ProfileDTO profile; }
public class UserEntity { private String username; private ProfileEntity profile; }
// Execution
ProfileDTO profileDto = new ProfileDTO("John", "Doe");
UserDTO userDto = new UserDTO();
userDto.setUsername("jdoe");
userDto.setProfile(profileDto);
UserEntity entity = mapper.map(userDto, UserEntity.class);
System.out.println(entity.getProfile().getFirstName()); // John
Dozer instantiated a new ProfileEntity, copied firstName and lastName from the DTO profile, and assigned it to the entity — all without a single line of mapping configuration.
4. Collection Mapping with Custom Converters
Mapping a List<UserDTO> to List<UserEntity> works out of the box. For type-incompatible collections (e.g., List<String> → List<TagEntity>), implement a DozerConverter.
public class StringToTagConverter extends DozerConverter<String, TagEntity> {
public StringToTagConverter() {
super(String.class, TagEntity.class);
}
@Override
public TagEntity convertTo(String source, TagEntity destination) {
if (source == null) return null;
TagEntity tag = new TagEntity();
tag.setName(source);
return tag;
}
@Override
public String convertFrom(TagEntity source, String destination) {
return source != null ? source.getName() : null;
}
}
Mapper mapper = DozerBeanMapperBuilder.create()
.withCustomConverter(new StringToTagConverter())
.build();
UserDTO dto = new UserDTO();
dto.setTags(Arrays.asList("Java", "Spring Boot", "Dozer"));
UserEntity entity = mapper.map(dto, UserEntity.class);
entity.getTags().forEach(tag -> System.out.println(tag.getName()));
// Output: Java / Spring Boot / Dozer
5. Spring Integration
In a Spring Boot application, inject the singleton Mapper bean into your service layer. This keeps mapping logic out of your controllers and repositories.
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private Mapper mapper;
public UserDTO createUser(UserDTO dto) {
UserEntity entity = mapper.map(dto, UserEntity.class);
UserEntity saved = userRepository.save(entity);
return mapper.map(saved, UserDTO.class); // bidirectional
}
}
6. Dozer vs MapStruct: Benchmark & Decision Guide
MapStruct is Dozer’s most prominent modern alternative. Unlike Dozer’s runtime reflection, MapStruct generates plain Java mapping code at compile time via annotation processing. Here is how they compare across the dimensions that matter most.
Performance Benchmark
The following figures are representative of JMH micro-benchmark results widely reported in the community for simple flat-object mapping (1,000,000 iterations, JVM warmed up):
| Library | Throughput (ops/ms) | Relative to Manual | Startup Cost |
|---|---|---|---|
| Manual (getter/setter) | ~12,000 | Baseline | None |
| MapStruct | ~11,500 | ~96% of manual | None (compile-time) |
| Dozer 6.5 | ~800 | ~7% of manual | Medium (first-call metadata parse) |
Note: Exact numbers vary with JVM version, object complexity, and warmup. The key takeaway is the order-of-magnitude difference, not the specific values.
Feature Comparison
| Feature | Dozer 6.5 | MapStruct 1.5+ |
|---|---|---|
| Mapping approach | Runtime reflection | Compile-time code generation |
| Performance | ~7–15% of manual | ~95–99% of manual |
| Setup complexity | Low — no build plugin needed | Medium — requires annotation processor |
| Type-mismatch config | Programmatic API or XML | Annotations on mapper interface |
| Null handling | Automatic (maps null) | Configurable per method |
| Compile-time safety | ❌ Runtime failures | ✅ Build-time errors |
| Deep/nested mapping | ✅ Automatic | ✅ With @Mapping(target=…) |
| Bidirectional | ✅ One config | ⚠️ Two mapper methods |
| Spring CDI injection | ✅ @Bean / @Component | ✅ @Mapper(componentModel=”spring”) |
| Active maintenance | Minimal (6.5 is latest) | Active (regular releases) |
When to Choose Dozer
- You are maintaining a legacy codebase already using Dozer — migration cost outweighs the performance gain for non-critical paths.
- Your models are not annotation-friendly (e.g., third-party classes you cannot modify) — Dozer can map them without touching the source or destination class.
- You need very dynamic, runtime-configurable mappings that change based on application state or tenant context.
- The mapped objects are on a non-critical path (admin screens, batch jobs, infrequent reports) where 7% vs 99% throughput is irrelevant.
When to Choose MapStruct
- You are building a new project and can set up the annotation processor from day one.
- Performance is critical — high-throughput APIs where mapping runs on every request benefit enormously from near-manual speed.
- You want compile-time safety — MapStruct fails the build if a target field has no mapping source, catching errors before they reach production.
- You prefer readable, debuggable generated code — MapStruct’s output is plain Java you can step through in a debugger.
7. Key Best Practices for Dozer
- Singleton Mapper: Create once, reuse everywhere. Never instantiate inside a loop or per-request.
- Exclude heavyweight fields: If your entity has Hibernate-managed lazy collections, configure field exclusions so Dozer does not trigger unintended database queries by calling the getter.
- Guard against null IDs: When mapping DTOs back to entities for updates, ensure the entity ID is preserved and not overwritten with
nullfrom the DTO. - Version-lock your dependency: Dozer 6.5.0 is the current stable release. The project is in maintenance mode, so pin the version explicitly in your POM.
Frequently Asked Questions
Q1: Is Dozer still maintained in 2026?
Dozer 6.5.0 (released in 2021) is the final stable release under active support. The project has moved to maintenance-only mode — critical bugs are addressed, but no new features are planned. For new projects, MapStruct or ModelMapper are more actively developed alternatives. For existing Dozer projects, 6.5.0 works reliably on Java 17 and Spring Boot 3, so there is no urgent migration pressure unless you hit a specific bug.
Q2: Does Dozer work with Java Records?
Partially. Dozer relies on the JavaBeans convention — it expects a no-arg constructor and getter/setter pairs. Java Records are immutable (no setters, no no-arg constructor), so Dozer cannot map into a Record as a destination. Records can be used as the source (Dozer reads via accessors), but the destination must be a standard POJO. For Record destinations, use MapStruct which has explicit support, or write a manual factory method.
Q3: How does Dozer handle null source values?
By default, Dozer copies null values from source to destination — if the source field is null, the destination field will be set to null as well. If you want to skip null values (i.e., only overwrite non-null fields), configure the mapping with mapNull(false): mapping(Src.class, Dest.class).mapNull(false). This is useful when doing partial updates where the DTO may contain only the fields that changed.
Q4: Will Dozer trigger Hibernate lazy loading on entity fields?
Yes — and this is one of the most common Dozer pitfalls in Hibernate applications. When Dozer calls the getter of a lazily-loaded collection or association, it triggers the Hibernate proxy to initialise and execute a SELECT query. If the Hibernate session is already closed, this causes a LazyInitializationException. The fix: use the BeanMappingBuilder to explicitly exclude lazy fields you don’t need in the DTO, or ensure mapping happens inside an active @Transactional boundary.
Q5: Can I use Dozer and MapStruct together in the same project?
Yes. They are independent libraries with no conflicts. A common migration strategy is to introduce MapStruct for new, performance-sensitive mapping paths (e.g., high-throughput API endpoints) while leaving existing Dozer mappings in place. Over time, you migrate legacy Dozer mappings to MapStruct incrementally. Both can be Spring beans injected by interface or type without interference.
Conclusion
Dozer remains a mature, battle-tested bean mapper that dramatically reduces DTO-to-entity boilerplate in Java applications. Its convention-over-configuration approach means that identical fields map automatically, nested objects are handled recursively without extra code, and collections are converted with minimal configuration. The trade-off is runtime performance: Dozer’s reflection-based approach is roughly 7–15x slower than MapStruct’s compile-time generated code. For legacy codebases, infrequent mapping paths, or scenarios requiring dynamic runtime configuration, Dozer is still a solid choice. For new high-throughput projects, MapStruct is the modern standard. Understanding both tools — and knowing when each is appropriate — is the mark of a pragmatic Java engineer.
Further Reading & Cross-References
- 📘 Hibernate 7 Entity Lifecycle — understanding Persistent vs Detached state when passing entities to Dozer
- 📘 Mastering Lazy Loading in Hibernate 7 — avoiding LazyInitializationException when Dozer accesses entity getters
- 📘 Hibernate 7 Proxies — how Hibernate proxy classes interact with Dozer’s reflection-based field access
- 📘 Entity Equality Across Sessions — ensuring IDs and business keys survive DTO-to-entity round-trips
- 🔗 Official Dozer User Guide
- 🔗 MapStruct Official Reference Documentation