Backend Development 13 min read

Understanding DTO, VO, PO and Object Mapping with BeanUtils and MapStruct in Java

This article explains the roles of DTO, VO, and PO in Java applications, compares manual property copying with BeanUtils and MapStruct, provides complete code examples for both approaches, and offers guidance on choosing the appropriate method based on performance and project needs.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding DTO, VO, PO and Object Mapping with BeanUtils and MapStruct in Java

In Java projects, DTO (Data Transfer Object), VO (Value Object), and PO (Persistent Object) each serve distinct purposes: DTOs carry data between client and server or between micro‑services, VOs represent data presented to the user, and POs map directly to database tables.

When converting between these objects, developers often start with manual set() calls, which becomes tedious and error‑prone as the number of fields grows.

Spring's BeanUtils offers a quick way to copy matching properties via reflection, but it incurs runtime overhead due to repeated type checks and field traversal.

MapStruct generates type‑safe mapper implementations at compile time, producing plain set() calls that run much faster. The article includes visual comparisons of BeanUtils source code and the compiled code produced by MapStruct.

Below is a custom utility class that extends BeanUtils to provide fluent conversion methods for single objects and lists:

import org.springframework.beans.BeanUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
 * Conversion utility extending BeanUtils
 */
public class BeanConvertUtils extends BeanUtils {
    public static
T convertTo(S source, Supplier
targetSupplier) {
        return convertTo(source, targetSupplier, null);
    }
    public static
T convertTo(S source, Supplier
targetSupplier, ConvertCallBack
callBack) {
        if (source == null || targetSupplier == null) {
            return null;
        }
        T target = targetSupplier.get();
        copyProperties(source, target);
        if (callBack != null) {
            callBack.callBack(source, target);
        }
        return target;
    }
    public static
List
convertListTo(List
sources, Supplier
targetSupplier) {
        return convertListTo(sources, targetSupplier, null);
    }
    public static
List
convertListTo(List
sources, Supplier
targetSupplier, ConvertCallBack
callBack) {
        if (sources == null || targetSupplier == null) {
            return null;
        }
        List
list = new ArrayList<>(sources.size());
        for (S source : sources) {
            T target = targetSupplier.get();
            copyProperties(source, target);
            if (callBack != null) {
                callBack.callBack(source, target);
            }
            list.add(target);
        }
        return list;
    }
    @FunctionalInterface
    public interface ConvertCallBack
{
        void callBack(S s, T t);
    }
}

A test class demonstrates converting a User object and a list of users to UserDTO using both the simple and lambda‑style APIs.

@Test
public void test04() {
    // copy Object
    User user = new User().setAge("20").setName("xxw");
    System.out.println("复制前的数据:" + user);
    UserDTO userDTO = BeanConvertUtils.convertTo(user, UserDTO::new);
    System.out.println("复制后的数据:" + userDTO);
    UserDTO dto = BeanConvertUtils.convertTo(user, UserDTO::new, (u, d) -> d.setAge(Integer.valueOf(u.getAge())));
    System.out.println("复制后的数据:" + dto);
    // copy List
User user1 = new User().setAge("22").setName("xw");
    List
users = Lists.newArrayList(user1);
    System.out.println("复制前的数据:" + users);
    List
dtos = BeanConvertUtils.convertListTo(users, UserDTO::new);
    System.out.println("复制后的数据:" + dtos);
    List
dtoList = BeanConvertUtils.convertListTo(users, UserDTO::new, (u, d) -> d.setAge(Integer.valueOf(u.getAge())));
    System.out.println("复制后的数据:" + dtoList);
}

For compile‑time mapping, the article shows how to add the MapStruct dependency (version 1.4.2.Final) and the required Maven compiler plugin configuration for JDK 11+, then defines DTO and PO classes annotated with Lombok.

@Data
@Accessors(chain = true)
public class BeanDto {
    private String name;
    private Integer age;
    private String time;
    private List
list;
    private Set
set;
}

@Data
@Accessors(chain = true)
public class BeanPo {
    private String name;
    private String age;
    private String newTime;
    private List
list1;
    private Set
set1;
}

The mapper interface uses @Mapper and @Mappings to map fields with different names and to convert types via custom @Named methods.

@Mapper
public interface BeanConvert {
    BeanConvert INSTANCE = Mappers.getMapper(BeanConvert.class);
    BeanDto poToDto(BeanPo po);
    @Mappings({
        @Mapping(source = "time", target = "newTime"),
        @Mapping(source = "set", target = "set1"),
        @Mapping(target = "age", qualifiedByName = "intToString"),
        @Mapping(source = "list", target = "list1", qualifiedByName = "intToStringOnList")
    })
    BeanPo dtoToPo(BeanDto dto);
    @Named("intToString")
    static String intToString(Integer num) { return String.valueOf(num); }
    @Named("intToStringOnList")
    static List
intToString(List
nums) { return nums.stream().map(String::valueOf).collect(Collectors.toList()); }
    static void main(String[] args) {
        BeanDto dto = new BeanDto().setName("dto").setAge(1).setTime("2022").setList(Lists.newArrayList(1,2)).setSet(Sets.newSet("2"));
        BeanPo beanPo = BeanConvert.INSTANCE.dtoToPo(dto);
        System.out.println(beanPo);
    }
}

The final recommendation is: use BeanUtils for quick, low‑performance‑critical scenarios without extra dependencies; use MapStruct when runtime efficiency matters or when you need compile‑time safety, remembering to verify that source and target field names and types match to avoid mapping errors.

BackendJavaDTOBeanUtilsMapStructObject MappingPOVO
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.