Summary
If a bean class contains multiple getter/setter methods with a same name but different types, the binding mechanism does not sort these methods properly which may lead to a non-deterministic/erroneous behaviour.
Affects v2.7.5 (current main).
Details
Let's have following bean classes:
static class Child extends Parent<ChildProperty> {
@Override
public ChildProperty getProperty() {
return null;
}
}
abstract static class Parent<T extends ParentProperty> {
abstract public T getProperty();
}
static class ChildProperty extends ParentProperty {
}
abstract static class ParentProperty {
}
When compiled, the Child class does have two getter methods. One from the Parent class with the ParentProperty return type, another from it's own implementation with the ChildPropertyReturnType.
$ javap Child.class
Compiled from "Child.java"
class Child extends Parent<ChildProperty> {
Child();
public ChildProperty getProperty();
public ParentProperty getProperty();
}
The binding mechanism in the JavaBeanBinder.Bean.addProperties() method, reads all declared methods via reflection and then tries to sort those methods by its name. Reason of sorting is non-determinism of the Class.getDeclaredMethods() reflection method. This issue was previously noted in #24068
https://github.com/spring-projects/spring-boot/blob/v2.7.5/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java#L130-L143
The problems is: Sorting is solely based on method name which does not (fully) remove the aforementioned non-determinism.
Additional details
- I've encountered this issue in Kotlin, where constructs like
abstract var property: T are slightly more common.
- Here is a test app FieldsOrderingTest.txt (please rename the
txt extension to java)
- Maybe unexpectedly Java compiler does not flag the
public ParentProperty getProperty() on the Child class as abstract. The following code returns array of false values:
Object[] abstracts = Arrays.stream(Child.class.getDeclaredMethods())
.map(Method::getModifiers)
.map(Modifier::isAbstract)
.toArray();
// abstracts = {Object[2]@710}
// 0 = {Boolean@712} false
// 1 = {Boolean@712} false
This means the JavaBeanBinder.Bean.isCandidate() method returns true for both getProperty() methods in the Child class.
Summary
If a bean class contains multiple getter/setter methods with a same name but different types, the binding mechanism does not sort these methods properly which may lead to a non-deterministic/erroneous behaviour.
Affects v2.7.5 (current main).
Details
Let's have following bean classes:
When compiled, the
Childclass does have two getter methods. One from theParentclass with theParentPropertyreturn type, another from it's own implementation with theChildPropertyReturnType.The binding mechanism in the
JavaBeanBinder.Bean.addProperties()method, reads all declared methods via reflection and then tries to sort those methods by its name. Reason of sorting is non-determinism of theClass.getDeclaredMethods()reflection method. This issue was previously noted in #24068https://github.com/spring-projects/spring-boot/blob/v2.7.5/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java#L130-L143
The problems is: Sorting is solely based on method name which does not (fully) remove the aforementioned non-determinism.
Additional details
abstract var property: Tare slightly more common.txtextension tojava)public ParentProperty getProperty()on theChildclass as abstract. The following code returns array of false values:JavaBeanBinder.Bean.isCandidate()method returns true for bothgetProperty()methods in theChildclass.