Dozer Bean Mapping Example and Migration to MapStruct

Dozer bean mapping with Dozer 7 (XML, annotations, API, custom converters), its inactive status, and how to migrate to MapStruct or ModelMapper.

Dozer mapping Recipe to RecipeDto with reflection at runtime, compared with MapStruct generating mapping code at compile time and ModelMapper matching at runtime

Dozer bean mapping copies data from one Java object to another, field by field, matching fields by name and converting types such as String to int on the way. The project is no longer active, its last release is 7.0.0, and its own README recommends moving to MapStruct or ModelMapper.

We use a bean mapper to turn entities into DTOs, API requests into domain objects and legacy service responses into our own classes without writing dozens of setX(getX()) lines. This guide shows the Dozer basics for maintaining existing code, and the same mappings with MapStruct and ModelMapper for migrating away from it.

The following example maps a Recipe bean to a RecipeDto with Dozer 7.0.0.

Recipe recipe = new Recipe();
recipe.setName("Pancakes");
recipe.setMinutes("20");                         // String in the source
recipe.setChef("Anna");

var mapper = DozerBeanMapperBuilder.buildDefault();
RecipeDto dto = mapper.map(recipe, RecipeDto.class);
String name = dto.getName();                     // "Pancakes"
int minutes = dto.getMinutes();                  // 20, converted to int
String author = dto.getAuthor();                 // null, no field with that name in Recipe

Notice that Dozer copies name and converts minutes on its own, but leaves author empty because the source calls it chef. We look at the project status, the Maven setup, XML, annotation and API mappings, custom converters, Spring integration, and the migration to MapStruct and ModelMapper.

1. What Is a Bean Mapper and Why Use It?

Apps often hold the same data in several shapes. A JPA entity, the DTO that a REST endpoint returns and the request object of an external service share most fields, and copying them by hand means long lists of getter and setter calls that break when somebody adds a field.

A bean mapper does the copying. It finds fields with the same name in the source and the target, converts the values when the types differ, and maps nested objects and collections. Configuration is only needed for the fields that differ, such as chef to author.

Dozer mapping Recipe to RecipeDto with reflection at runtime, compared with MapStruct generating mapping code at compile time and ModelMapper matching at runtime
Dozer and ModelMapper match fields with reflection at runtime, whereas MapStruct generates plain Java mapping code at compile time

The three libraries in this guide differ in when the mapping is worked out.

DozerModelMapperMapStruct
How it mapsReflection at runtimeReflection at runtime, smart name matchingGenerated Java code at compile time
ConfigurationXML, annotations or Java APIJava API (TypeMap)Annotations on an interface
Errors in mappings foundAt runtimeAt runtime, or at startup with validate()At compile time
Java records as targetNoOnly with an extra moduleYes
StatusNot active, 7.0.0 (March 2024)Active, 3.2.7Active, 1.6.3 stable

2. Is Dozer Still Maintained?

No. The Dozer README says that the project is currently not active and will more than likely be deprecated, and it advises against using it in new projects. The latest release is 7.0.0 from March 2024, under the coordinates com.github.dozermapper:dozer-core.

The older coordinates net.sf.dozer:dozer with version 5.5.1 and the package org.dozer, which many tutorials still show, date from 2014. Dozer 7.0.0 still runs on Java 25, but it pulls in commons-beanutils 1.9.4, which is affected by CVE-2025-48734, so dependency scanners flag it.

Keep Dozer only in code that already uses it, and plan the move to MapStruct or ModelMapper. Until the migration is done, we pin commons-beanutils to 1.11.0 or later in the build to fix the known vulnerability.

3. Dozer Maven Dependency

Dozer 7 uses the com.github.dozermapper.core package. Upgrading from 5.5.1 means changing the imports from org.dozer and the XML namespace, as shown in section 5.

<dependency>
    <groupId>com.github.dozermapper</groupId>
    <artifactId>dozer-core</artifactId>
    <version>7.0.0</version>
</dependency>
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.11.0</version>             <!-- fixes CVE-2025-48734 -->
</dependency>

4. Simple Dozer Bean Mapping Example

Dozer works with JavaBeans, so every class needs a public no-argument constructor plus getters and setters. The following example uses a Recipe with a list of Step objects, and a RecipeDto with a list of StepDto objects. The minutes field is a String in the source, as it often is in data from forms or old systems, and an int in the target.

public static class Step {
    private String text;
    public Step() { }
    public Step(String text) { this.text = text; }
    public String getText() { return text; }
    public void setText(String text) { this.text = text; }
}
public static class Recipe {
    private String name;
    private String minutes;
    private String chef;
    private List<Step> steps = new ArrayList<>();
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public String getMinutes() { return minutes; }
    public void setMinutes(String minutes) { this.minutes = minutes; }
    public String getChef() { return chef; }
    public void setChef(String chef) { this.chef = chef; }
    public List<Step> getSteps() { return steps; }
    public void setSteps(List<Step> steps) { this.steps = steps; }
}
public static class StepDto {
    private String text;
    public String getText() { return text; }
    public void setText(String text) { this.text = text; }
}
public static class RecipeDto {
    private String name;
    private int minutes;
    private String author;
    private Duration cookTime;
    private List<StepDto> steps;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getMinutes() { return minutes; }
    public void setMinutes(int minutes) { this.minutes = minutes; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    public Duration getCookTime() { return cookTime; }
    public void setCookTime(Duration cookTime) { this.cookTime = cookTime; }
    public List<StepDto> getSteps() { return steps; }
    public void setSteps(List<StepDto> steps) { this.steps = steps; }
}

A Mapper built by DozerBeanMapperBuilder is thread-safe and expensive to create, because it scans the classes and caches the mappings. We build it once and reuse it, for example as a singleton or a Spring bean, instead of creating a new mapper for every call.

Recipe recipe = new Recipe();
recipe.setName("Pancakes");
recipe.setMinutes("20");
recipe.getSteps().add(new Step("Mix"));
recipe.getSteps().add(new Step("Fry"));

var mapper = DozerBeanMapperBuilder.buildDefault();
RecipeDto dto = mapper.map(recipe, RecipeDto.class);
int steps = dto.getSteps().size();               // 2
String second = dto.getSteps().get(1).getText(); // "Fry"

recipe.setMinutes("twenty");
RecipeDto broken = mapper.map(recipe, RecipeDto.class);   // ConversionException: Error converting from 'String' to 'Integer' For input string: "twenty"

Dozer created a StepDto for each Step, because the element types of the two lists are known from their generic types. A value that cannot be converted fails at runtime with ConversionException, so mapping code needs tests that cover bad input.

5. Dozer Mapping with an XML File

Fields with different names need configuration. The classic way is an XML mapping file on the classpath. Dozer 7 uses the namespace http://dozermapper.github.io/schema/bean-mapping, and files with the old dozer.sourceforge.net namespace must be updated.

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
                              https://dozermapper.github.io/schema/bean-mapping.xsd">
    <mapping>
        <class-a>com.howtodoinjava.recipes.Recipe</class-a>
        <class-b>com.howtodoinjava.recipes.RecipeDto</class-b>
        <field>
            <a>chef</a>
            <b>author</b>
        </field>
    </mapping>
</mappings>

The builder loads the file with withMappingFiles(“recipe-mapping.xml”). Mappings are bidirectional by default, so the same file also maps author back to chef when we map a RecipeDto to a Recipe.

static com.github.dozermapper.core.Mapper buildXmlMapper() {
    return DozerBeanMapperBuilder.create()
            .withMappingFiles("recipe-mapping.xml")
            .build();
}

6. Dozer Mapping with Annotations

The @Mapping annotation of Dozer sets the target field name on the source field. It works for simple renames when we can change the source class, and it keeps the mapping next to the field.

public static class Chef {
    @com.github.dozermapper.core.Mapping("author")
    private String fullName;
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
}
Chef chef = new Chef();
chef.setFullName("Anna Lee");
RecipeDto fromChef = DozerBeanMapperBuilder.buildDefault().map(chef, RecipeDto.class);
String annotated = fromChef.getAuthor();         // "Anna Lee"

The full annotation name avoids a clash with MapStruct’s @Mapping, which has the same simple name. During a migration, both annotations often appear in the same codebase.

7. Dozer Mapping with the Java API

The BeanMappingBuilder class declares the same mappings in Java code, which avoids XML files and gets checked by the compiler for class names. Field names are still plain strings and are checked only at runtime.

static BeanMappingBuilder recipeMapping() {
    return new BeanMappingBuilder() {
        @Override
        protected void configure() {
            mapping(Recipe.class, RecipeDto.class)
                    .fields("chef", "author")
                    .fields("minutes", "cookTime", FieldsMappingOptions.customConverter(MinutesConverter.class));
        }
    };
}
Recipe recipe = new Recipe();
recipe.setName("Soup");
recipe.setMinutes("45");
recipe.setChef("Ben");
var apiMapper = DozerBeanMapperBuilder.create().withMappingBuilder(recipeMapping()).build();
RecipeDto mapped = apiMapper.map(recipe, RecipeDto.class);
String apiAuthor = mapped.getAuthor();           // "Ben"
Duration cookTime = mapped.getCookTime();        // PT45M
int plainMinutes = mapped.getMinutes();          // 0, not mapped any more

The last line shows a Dozer rule that surprises people. Once a source field appears in an explicit mapping, Dozer no longer maps it by name, so minutes goes only to cookTime, and the minutes field of the DTO stays 0. To fill both, we add .fields(“minutes”, “minutes”) to the mapping.

8. Dozer Custom Converter Example

A custom converter handles values that need logic, such as parsing a unit or combining fields. It extends DozerConverter<A, B>, where convertTo() turns an A into a B and convertFrom() turns a B back into an A.

public static class MinutesConverter extends DozerConverter<String, Duration> {
    public MinutesConverter() {
        super(String.class, Duration.class);
    }

    @Override
    public Duration convertTo(String minutes, Duration ignored) {
        return minutes == null ? null : Duration.ofMinutes(Long.parseLong(minutes.strip()));
    }

    @Override
    public String convertFrom(Duration duration, String ignored) {
        return duration == null ? null : String.valueOf(duration.toMinutes());
    }
}

The two types must differ, because with DozerConverter<String, String> Dozer cannot tell the directions apart. For a conversion between two values of the same type, we implement the plain CustomConverter interface instead. The mapping from section 7 registers MinutesConverter for one field, and the reverse direction works too.

RecipeDto dto = new RecipeDto();
dto.setCookTime(Duration.ofMinutes(90));
var reverseMapper = DozerBeanMapperBuilder.create().withMappingBuilder(recipeMapping()).build();
Recipe back = reverseMapper.map(dto, Recipe.class);
String backMinutes = back.getMinutes();          // "90"

9. Using Dozer with Spring

In a Spring application, the mapper is a singleton bean that services inject. Dozer 7 also has a dozer-spring-boot-starter, but a plain @Bean method is enough and keeps the setup visible.

@Bean
public com.github.dozermapper.core.Mapper dozerMapper() {
    return DozerBeanMapperBuilder.create()
            .withMappingFiles("recipe-mapping.xml")
            .build();
}

The XML-based DozerBeanMapper bean definition from Dozer 5 no longer works with Dozer 7, because org.dozer.DozerBeanMapper is gone and the constructor-based setup was replaced by the builder.

10. Migrating from Dozer to MapStruct

A recipe app maps 40 entity and DTO pairs with Dozer XML files. A renamed field in an entity breaks a mapping without any compile error, and the bug shows up as an empty field in the mobile app. MapStruct moves the mapping to compile time, so the same rename becomes a build error.

MapStruct 1.6.3 generates a plain Java class from an annotated interface. The generated code calls getters and setters, so there is no reflection at runtime, and it maps into records through their constructors.

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.6.3</version>
</dependency>
<!-- and in maven-compiler-plugin -->
<annotationProcessorPaths>
    <path>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.6.3</version>
    </path>
</annotationProcessorPaths>
record RecipeView(String name, int minutes, String author) {}
@Mapper
public interface RecipeMapper {
    @Mapping(target = "author", source = "chef")
    RecipeView toView(Recipe recipe);
}
static RecipeView toView(Recipe recipe) {
    RecipeMapper mapper = Mappers.getMapper(RecipeMapper.class);
    return mapper.toView(recipe);    // RecipeView[name=Pancakes, minutes=20, author=Anna]
}

For the Pancakes recipe from the intro, toView() returns RecipeView[name=Pancakes, minutes=20, author=Anna]. The processor generates RecipeMapperImpl with Integer.parseInt() for minutes and a null check, which we can read in target/generated-sources. Since JDK 23, javac runs annotation processors only when they are configured, so the annotationProcessorPaths entry is required.

In Spring, @Mapper(componentModel = “spring”) turns the generated class into a bean that we inject instead of calling Mappers.getMapper().

11. Migrating from Dozer to ModelMapper

ModelMapper is closer to Dozer, because it also maps at runtime with reflection and matches fields by name. It needs no annotation processor, which makes it the smaller step when a project has many dynamic mappings.

ModelMapper modelMapper = new ModelMapper();
modelMapper.typeMap(Recipe.class, RecipeDto.class)
        .addMapping(Recipe::getChef, RecipeDto::setAuthor);

Recipe recipe = new Recipe();
recipe.setName("Pancakes");
recipe.setMinutes("20");
recipe.setChef("Anna");
RecipeDto viaModelMapper = modelMapper.map(recipe, RecipeDto.class);
String mmAuthor = viaModelMapper.getAuthor();     // "Anna"
int mmMinutes = viaModelMapper.getMinutes();      // 20

The addMapping() method uses method references, so a renamed getter is a compile error, unlike the strings in Dozer. Calling modelMapper.validate() at startup checks that every target field is mapped. ModelMapper 3.2.7 maps to records only with the separate modelmapper-module-record module, and it prints a sun.misc.Unsafe warning on Java 25.

12. Dozer FAQs

Teams that inherit Dozer code ask about its future first, followed by the cost and the details of moving away from it.

12.1. Is Dozer deprecated?

Not formally, but the README marks the project as not active and likely to be deprecated, and it recommends MapStruct or ModelMapper. Dozer 7.0.0 still works, but it gets no fixes, so new code should not depend on it.

12.2. Does Dozer work with Java 17 and Java 25?

Yes. Dozer 7.0.0 needs Java 11 or later, and it maps JavaBeans on Java 25. It cannot create records or other classes without a no-argument constructor, and fails with MappingException caused by NoSuchMethodException.

12.3. Is MapStruct faster than Dozer?

Yes. MapStruct generates plain getter and setter calls, so a mapping costs about as much as hand-written code. Dozer looks up fields with reflection and converts values through Commons BeanUtils on every call, which is much slower in mapping-heavy code.

12.4. Can Dozer do a deep copy of an object?

Yes. Mapping an object to its own class, as in mapper.map(recipe, Recipe.class), creates new instances of the object, its nested beans and its collections. For copying within one class, a copy constructor or a deep copy written by hand is simpler and has no reflection cost.

12.5. How do I map a field to a different name in MapStruct?

We add @Mapping(target = “author”, source = “chef”) to the mapper method. Nested properties use dots, such as source = “chef.name”, and ignore = true skips a target field.

13. Conclusion

Dozer copies JavaBeans field by field with reflection, converts common types and supports XML files, the @Mapping annotation, the BeanMappingBuilder API and custom converters. Dozer 7 needs the com.github.dozermapper coordinates, the builder and the new XML namespace, and the project is no longer active.

For existing code, we build the mapper once, pin a fixed commons-beanutils version and test the mappings. For new code and migrations, MapStruct finds mapping errors at compile time and supports records, while ModelMapper keeps the runtime approach of Dozer with method references instead of strings.

14. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. <bean id="dozerBeanMapper" class="org.dozer.DozerBeanMapper">
            <property name="mappingFiles">
                <list>
                    <value>custom-converter-mapping.xml</value>
                </list>
            </property>
        </bean>
    

    In this section please change the value to custom-converter-spring-mapping.xml

  2. how to convert from list of object A to list of string.
    explanation: suppose my object A is Person in which there are three fields id, name, and dob. And I want to map name field to the list of string.
    If anyone have the answer please reply.

  3. Your code is not conforming with what you are writing. Java Beans? They are serializable, your sample classes are not…

    And otherwise: DTOs are anti-patterns (yes they are, and everyone fails to realize that). So you are using a tool (Dozer) to leverage an anti-pattern. Nice!

    • 1) You are right about serialization. All beans should implement Serializable interface. I will update the post for this.
      2) DTOs are anti-patterns. It’s not 100% correct for me. I have seen cases where business objects coming via services have just too many fields and I cannot risk them sending to UI or API response, just as it. So one must create one DTO with only necessary fields, and then can use Dozer to copy this information in just 2-3 lines.

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.