Skip to content

Configuration property binding does not deal with bridge methods #33105

@vkuzel

Description

@vkuzel

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions