Ignore static get/is accessor methods in PropertyDescriptorUtils

Prior to this commit, PropertyDescriptorUtils.determineBasicProperties()
incorrectly recognized static `get` and `is` accessor methods as
JavaBean read methods, in contrast to the standard
java.beans.Introspector, which has always excluded static methods from
property discovery. This regression was introduced in Spring Framework
6.0 when determineBasicProperties() replaced the delegation to
java.beans.Introspector for the fast property-discovery path used by
SimpleBeanInfoFactory. As a result, an unrelated static method such as
a singleton accessor could be exposed as a bean property, and
reflective access to such a property (for example, via BeanWrapperImpl)
could lead to a StackOverflowError if the property's value recursively
exposed the same static accessor.

To address that, this commit adds Modifier.isStatic(...) checks to the
`get` and `is` branches in determineBasicProperties(), mirroring the
equivalent check already present in
CachedIntrospectionResults.isPlainAccessor(). Static `set` methods
continue to be supported as write methods, consistent with the existing
behavior in ExtendedBeanInfo.

See gh-37068
Closes gh-37081

Signed-off-by: Arnab Nandy <arnab_nandy7@yahoo.com>
This commit is contained in:
Arnab Nandy
2026-07-30 14:12:08 +02:00
committed by GitHub
parent 50f923ace4
commit badddeb0dc
2 changed files with 23 additions and 2 deletions
@@ -19,6 +19,7 @@ package org.springframework.beans;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
@@ -71,11 +72,13 @@ abstract class PropertyDescriptorUtils {
setter = true;
nameIndex = 3;
}
else if (methodName.startsWith("get") && method.getParameterCount() == 0 && method.getReturnType() != void.class) {
else if (methodName.startsWith("get") && method.getParameterCount() == 0 &&
method.getReturnType() != void.class && !Modifier.isStatic(method.getModifiers())) {
setter = false;
nameIndex = 3;
}
else if (methodName.startsWith("is") && method.getParameterCount() == 0 && method.getReturnType() == boolean.class) {
else if (methodName.startsWith("is") && method.getParameterCount() == 0 &&
method.getReturnType() == boolean.class && !Modifier.isStatic(method.getModifiers())) {
setter = false;
nameIndex = 2;
}
@@ -69,6 +69,13 @@ class PropertyDescriptorUtilsPropertyResolutionTests {
assertReadAndWriteMethodsForClassAndId(pdMap, Number.class, null);
}
@Test
void classWithStaticGetters() {
var pdMap = resolver.resolve(ClassWithStaticGetters.class);
assertThat(pdMap).containsOnlyKeys("class");
}
@Test
void classWithOnlySetter() {
var pdMap = resolver.resolve(ClassWithOnlySetter.class);
@@ -134,6 +141,17 @@ class PropertyDescriptorUtilsPropertyResolutionTests {
}
}
static class ClassWithStaticGetters {
public static Long getId() {
return 42L;
}
public static boolean isActive() {
return true;
}
}
static class ClassWithOnlySetter {
public void setId(Long id) {