Backend Development 8 min read

Pitfalls of Java Bean Property Copy Tools and Recommended Alternatives

This article explains why generic Java bean property copy utilities like Spring BeanUtils, Apache Commons BeanUtils, and CGLIB can cause runtime type conversion errors and performance issues, demonstrates the problems with code examples, and recommends using custom converters or IDE‑generated mapping code such as MapStruct for safe and efficient object transformation.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Pitfalls of Java Bean Property Copy Tools and Recommended Alternatives

Background : The author previously warned against using generic property copy tools and suggested defining explicit conversion classes, leveraging IDE plugins to auto‑generate getter/setter methods.

Reasons not to use property copy tools :

Some tools have poor performance.

Some tools contain bugs.

They can hide hidden risks, such as type mismatches that only surface at runtime.

Example 1 – Spring BeanUtils : Using Spring's BeanUtils.copyProperties to copy from class A (with List<Integer> ) to class B (with List<String> ) leads to a ClassCastException at runtime.

import lombok.Data;

import java.util.List;

@Data
public class A {
    private String name;
    private List
ids;
}
@Data
public class B {
    private String name;
    private List
ids;
}
import org.springframework.beans.BeanUtils;
import java.util.Arrays;

public class BeanUtilDemo {
    public static void main(String[] args) {
        A first = new A();
        first.setName("demo");
        first.setIds(Arrays.asList(1, 2, 3));

        B second = new B();
        BeanUtils.copyProperties(first, second);
        for (String each : second.getIds()) { // type conversion exception
            System.out.println(each);
        }
    }
}

The code throws a type conversion exception because the generic type information is erased at runtime, and the copied list still holds Integer objects.

Example 2 – CGLIB BeanCopier : Using CGLIB's BeanCopier without a custom converter reproduces the same issue.

import org.easymock.cglib.beans.BeanCopier;
import java.util.Arrays;

public class BeanUtilDemo {
    public static void main(String[] args) {
        A first = new A();
        first.setName("demo");
        first.setIds(Arrays.asList(1, 2, 3));

        B second = new B();
        final BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false);
        beanCopier.copy(first, second, null);

        for (String each : second.getIds()) { // type conversion exception
            System.out.println(each);
        }
    }
}

Again, the problem surfaces only at runtime.

Example 3 – MapStruct : Using MapStruct generates a mapper that correctly converts List<Integer> to List<String> by applying a conversion method.

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface Converter {
    Converter INSTANCE = Mappers.getMapper(Converter.class);
    B aToB(A a);
}
import java.util.Arrays;

public class BeanUtilDemo {
    public static void main(String[] args) {
        A first = new A();
        first.setName("demo");
        first.setIds(Arrays.asList(1, 2, 3));

        B second = Converter.INSTANCE.aToB(first);
        for (String each : second.getIds()) { // works fine
            System.out.println(each);
        }
    }
}

The generated ConverterImpl includes a helper method that converts the integer list to a string list, demonstrating how compile‑time annotation processing can avoid the runtime pitfalls.

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
@Component
public class ConverterImpl implements Converter {
    @Override
    public B aToB(A car) {
        if (car == null) {
            return null;
        }
        B b = new B();
        b.setName(car.getName());
        b.setIds(integerListToStringList(car.getIds()));
        return b;
    }
    protected List
integerListToStringList(List
list) {
        if (list == null) {
            return null;
        }
        List
list1 = new ArrayList
(list.size());
        for (Integer integer : list) {
            list1.add(String.valueOf(integer));
        }
        return list1;
    }
}

Conclusion : Because Java generics are erased after compilation, runtime type checks cannot detect mismatched generic types, allowing unsafe assignments that cause hidden bugs. Tools like MapStruct perform compile‑time checks and generate safe conversion code, but developers must still verify type compatibility. The safest approach is to write explicit conversion classes (or use IDE‑generated code) so that mismatches are caught at compile time and performance remains optimal.

backendJavaBeanUtilsMapStructCglibPropertyCopy
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.