Why Java Bean Property Copy Tools Can Break Your Code: Hidden Type Issues
This article examines the pitfalls of Java property‑copy utilities such as Spring BeanUtils, CGLIB BeanCopier, and MapStruct, demonstrating performance drawbacks and runtime type‑conversion errors through concrete examples, and recommends defining explicit conversion classes or using IDE‑generated code to avoid hidden bugs.
1. Background
Previously it was recommended not to use generic property‑copy tools and instead define explicit conversion classes, letting the IDEA plugin auto‑generate getter/setter methods. The main reasons are poor performance, occasional bugs, and hidden risks that may surface at runtime.
2. Example
A real case in a company showed that commons-beanutils performed poorly for property copying, while Spring's BeanUtils was much faster. The following code demonstrates a type‑conversion problem when using Spring's BeanUtils.copyProperties:
import lombok.Data;
import java.util.List;
@Data
public class A {
private String name;
private List<Integer> ids;
}
@Data
public class B {
private String name;
private List<String> 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);
}
}
}Running the example throws a ClassCastException because the ids field in B is a List<String> while the source provides a List<Integer>. The issue only appears at runtime due to Java's type erasure.
Using CGLIB's BeanCopier without a custom converter exhibits the same problem:
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();
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);
}
}
}MapStruct can handle the conversion correctly by generating a mapper that converts List<Integer> to List<String>:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface Converter {
Converter INSTANCE = Mappers.getMapper(Converter.class);
B aToB(A car);
}
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 implementation ( ConverterImpl) automatically converts the list types, which can be convenient but also hides type mismatches:
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<String> integerListToStringList(List<Integer> list) {
if (list == null) {
return null;
}
List<String> list1 = new ArrayList<>(list.size());
for (Integer integer : list) {
list1.add(String.valueOf(integer));
}
return list1;
}
}If a String field is mapped to a Long field and the source contains a non‑numeric value, MapStruct will throw a NumberFormatException. CGLIB, by default, does not map such a field, leaving it null.
Manually writing a converter (or using an IDEA plugin like generateO2O) makes the mismatch visible at compile time:
public final class A2BConverter {
public static B from(A first) {
B b = new B();
b.setName(first.getName());
b.setIds(first.getIds());
return b;
}
}The IDE can highlight these mismatches early, preventing runtime errors.
3. Conclusion
Because Java generics are erased after compilation, List<Integer> and List<String> are both just List at runtime, allowing assignments that hide type errors. Property‑copy tools therefore may introduce subtle bugs that are not caught at compile time. MapStruct reads generic types during annotation processing and can perform automatic conversions, but careless mappings can still cause side effects. It is advisable to use explicit conversion classes or IDE‑generated code, which provide compile‑time safety and high performance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
