From ee0b99bc6797c1495f7cef82f5025c99c8d1de51 Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Sun, 23 Aug 2026 00:49:06 +0530 Subject: [PATCH 1/2] skip static methods in FluentPropertyBeanIntrospector and mapped lookup --- .../FluentPropertyBeanIntrospector.java | 7 +++- .../beanutils2/MappedPropertyDescriptor.java | 3 +- .../FluentPropertyBeanIntrospectorTest.java | 33 +++++++++++++++++++ .../beanutils2/MappedPropertyTest.java | 10 ++++++ .../beanutils2/MappedPropertyTestBean.java | 8 +++++ 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java index e40f09ef4..c10522117 100644 --- a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java +++ b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java @@ -20,6 +20,7 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Objects; import org.apache.commons.logging.Log; @@ -56,7 +57,7 @@ *

* This class is more tolerant with regards to the return type of a set method. It basically iterates over all methods of a class and filters them for a * configurable prefix (the default prefix is {@code set}). It then generates corresponding {@code PropertyDescriptor} objects for the methods found which use - * these methods as write methods. + * these methods as write methods. Static methods are ignored, as they are by default introspection. *

*

* An instance of this class is intended to collaborate with a {@link DefaultBeanIntrospector} object. So best results are achieved by adding this instance as @@ -128,6 +129,10 @@ public String getWriteMethodPrefix() { @Override public void introspect(final IntrospectionContext icontext) throws IntrospectionException { for (final Method m : icontext.getTargetClass().getMethods()) { + // Static methods are not property accessors; default introspection skips them as well. + if (Modifier.isStatic(m.getModifiers())) { + continue; + } if (m.getName().startsWith(getWriteMethodPrefix())) { final String propertyName = propertyName(m); final PropertyDescriptor pd = icontext.getPropertyDescriptor(propertyName); diff --git a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java index 012a3fbd6..dafba2fe5 100644 --- a/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java +++ b/src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java @@ -181,7 +181,8 @@ private static Method getMethod(final Class clazz, final String methodName, f } final Method method = MethodUtils.getMatchingAccessibleMethod(clazz, methodName, parameterTypes); - if (method != null) { + // skip static methods, as internalGetMethod does. + if (method != null && !Modifier.isStatic(method.getModifiers())) { return method; } diff --git a/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java b/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java index c89a86e43..1cc6ae139 100644 --- a/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java +++ b/src/test/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospectorTest.java @@ -16,6 +16,7 @@ */ package org.apache.commons.beanutils2; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -45,6 +46,23 @@ public void setURI(final URI theURI) { } } + public static final class StaticSetterBean { + private static String staticValue; + + public static void setStaticOnly(final String value) { + staticValue = value; + } + + public static StaticSetterBean setStaticProperty(final String value) { + staticValue = value; + return new StaticSetterBean(); + } + + public String getStaticProperty() { + return staticValue; + } + } + /** * Puts all property descriptors into a map so that they can be accessed by property name. * @@ -119,4 +137,19 @@ void testIntrospectionCaps() throws Exception { assertNull(props.get("uRI"), "Should not find mis-capitalized property"); } + + /** + * Tests that static methods are not treated as write methods. + */ + @Test + void testIntrospectionStaticMethods() throws Exception { + final PropertyUtilsBean pu = new PropertyUtilsBean(); + pu.addBeanIntrospector(new FluentPropertyBeanIntrospector()); + final Map props = createDescriptorMap(pu.getPropertyDescriptors(StaticSetterBean.class)); + assertNull(props.get("staticOnly"), "Property created from static method"); + final PropertyDescriptor pd = fetchDescriptor(props, "staticProperty"); + assertNotNull(pd.getReadMethod(), "No read method for staticProperty"); + assertNull(pd.getWriteMethod(), "Static method used as write method"); + assertFalse(pu.isWriteable(new StaticSetterBean(), "staticProperty"), "staticProperty is writeable"); + } } diff --git a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java index 425aa0b9d..f6d898bc5 100644 --- a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java +++ b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTest.java @@ -225,6 +225,16 @@ void testProtected() { assertThrows(IntrospectionException.class, () -> new MappedPropertyDescriptor(property, clazz)); } + /** + * Test static mapped accessors are ignored + */ + @Test + void testStaticMapped() { + final String property = "staticMapped"; + final Class clazz = MappedPropertyTestBean.class; + assertThrows(IntrospectionException.class, () -> new MappedPropertyDescriptor(property, clazz)); + } + /** * Test 'protected' method in parent */ diff --git a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java index cdd816414..69d7f56cd 100644 --- a/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java +++ b/src/test/java/org/apache/commons/beanutils2/MappedPropertyTestBean.java @@ -29,6 +29,14 @@ public class MappedPropertyTestBean { private final Map map = new HashMap<>(); private final Map myMap = new HashMap<>(); + public static String getStaticMapped(final String key) { + return "static-" + key; + } + + public static void setStaticMapped(final String key, final String value) { + // empty + } + public Long getDifferentTypes(final String key) { return Long.valueOf(((Number) map.get(key)).longValue()); } From c8036fb6d13b8d8bd9c40523f30a4d7b17d7e6c4 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Sun, 23 Aug 2026 09:31:27 -0400 Subject: [PATCH 2/2] Fix comment on static method introspection --- .../commons/beanutils2/FluentPropertyBeanIntrospector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java index c10522117..61e2f6e77 100644 --- a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java +++ b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java @@ -57,7 +57,7 @@ *

* This class is more tolerant with regards to the return type of a set method. It basically iterates over all methods of a class and filters them for a * configurable prefix (the default prefix is {@code set}). It then generates corresponding {@code PropertyDescriptor} objects for the methods found which use - * these methods as write methods. Static methods are ignored, as they are by default introspection. + * these methods as write methods. Static methods are ignored, as they are by default ignored in introspection. *

*

* An instance of this class is intended to collaborate with a {@link DefaultBeanIntrospector} object. So best results are achieved by adding this instance as