Why Spring BeanUtils Fails to Copy Static Inner Classes and How to Fix It

This article explains why Spring's BeanUtils.copyProperties cannot copy static inner class fields and list properties, demonstrates the pitfalls with missing getters/setters, and provides a step‑by‑step solution to correctly copy such objects in Java applications.

Programmer DD
Programmer DD
Programmer DD
Why Spring BeanUtils Fails to Copy Static Inner Classes and How to Fix It

Background: In a recent project we needed to send an HTTP request to a third‑party SDK. The SDK’s Request class had a bug, so we rewrote our own Request class with similar fields, including a static inner class and two List properties.

private List<Order> orders;
private AddRequest.Ticket ticket;
private List<Payment> payments;

We assembled the request parameters in our AddRequest class and used Spring’s BeanUtils.copyProperties to copy them into the SDK’s MixAddRequest before sending. The request should have succeeded, but the third‑party reported a missing required field inside the Ticket inner class.

Investigation revealed that BeanUtils.copyProperties did not copy the static inner class field, leaving it null. The first pitfall was that the fields were declared public without Lombok’s @Data, so no getters/setters existed and everything copied as null.

After adding @Data, simple fields (String) were copied, but the static inner class remained null, confirming the inner‑class issue.

@ToString
@Data
public class CopyTest1 {
    public String outerName;
    public CopyTest1.InnerClass innerClass;
    public List<CopyTest1.InnerClass> clazz;
    @ToString
    @Data
    public static class InnerClass {
        public String InnerName;
    }
}

@ToString
@Data
public class CopyTest2 {
    public String outerName;
    public CopyTest2.InnerClass innerClass;
    public List<CopyTest2.InnerClass> clazz;
    @ToString
    @Data
    public static class InnerClass {
        public String InnerName;
    }
}

CopyTest1 test1 = new CopyTest1();
test1.outerName = "hahaha";
CopyTest1.InnerClass innerClass = new CopyTest1.InnerClass();
innerClass.InnerName = "hohoho";
test1.innerClass = innerClass;
System.out.println(test1.toString());
CopyTest2 test2 = new CopyTest2();
BeanUtils.copyProperties(test1, test2);
System.out.println(test2.toString());

The second pitfall was that even with getters/setters, BeanUtils does not copy properties whose types are different static inner classes, because they are considered distinct types.

Solution: copy the inner class separately and ensure it also has setters.

CopyTest1 test1 = new CopyTest1();
test1.outerName = "hahaha";
CopyTest1.InnerClass innerClass = new CopyTest1.InnerClass();
innerClass.InnerName = "hohoho";
test1.innerClass = innerClass;
System.out.println(test1.toString());
CopyTest2 test2 = new CopyTest2();
test2.innerClass = new CopyTest2.InnerClass();
BeanUtils.copyProperties(test1, test2);
BeanUtils.copyProperties(test1.innerClass, test2.innerClass);
System.out.println(test2.toString());

Note that list properties also work because Java generics are erased at runtime; the List contains Objects, so the elements are copied without type conflicts.

Summary

Spring BeanUtils.copyProperties requires matching getter and setter methods for each property.

Static inner classes with identical field names but different enclosing classes are treated as different types and are not copied automatically.

Java generics only affect compile‑time; at runtime a List is just a collection of Objects.

The order of source and target parameters in BeanUtils (and Apache Commons) copy methods is crucial; swapping them leads to unexpected results.

Final Note

Spring’s source shows that copyProperties uses the target’s setter (write method); without a setter the copy fails.

private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {
    Assert.notNull(source, "Source must not be null");
    Assert.notNull(target, "Target must not be null");
    Class<?> actualEditable = target.getClass();
    if (editable != null) {
        if (!editable.isInstance(target)) {
            throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
        }
        actualEditable = editable;
    }
    PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
    List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
    for (PropertyDescriptor targetPd : targetPds) {
        Method writeMethod = targetPd.getWriteMethod();
        if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
            PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
            if (sourcePd != null) {
                Method readMethod = sourcePd.getReadMethod();
                if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                    try {
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        Object value = readMethod.invoke(source);
                        if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                            writeMethod.setAccessible(true);
                        }
                        writeMethod.invoke(target, value);
                    } catch (Throwable ex) {
                        throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                    }
                }
            }
        }
    }
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBeanUtilslombokstatic inner classCopyProperties
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.