From 0f0c7dd607c268a0efe6c3e32a40b2750c83dbb4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 23 Jun 2026 11:30:42 +0200 Subject: [PATCH 1/2] Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2ace266bf9..f623579bc9 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 4.2.0-SNAPSHOT + 4.2.0-GH-3500-SNAPSHOT Spring Data Core Core Spring concepts underpinning every Spring Data module. From 2fd2b8c8b06c62a6afeee31b3922b6d4495462df Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 23 Jun 2026 11:42:43 +0200 Subject: [PATCH 2/2] Retain subclass fields over masked superclass fields in `TypeDiscoverer`. When a class hides a superclass field by declaring a field of the same name, property discovery overwrote the property type from the superclass field instead of retaining the declaring subclass field. This happened because ReflectionUtils.doWithFields traverses fields from the leaf type up through its superclasses, and the field callback unconditionally put each field into the result map. As a result, the superclass field, visited last, overwrote the entry contributed by the subclass, exposing the wrong (and potentially incompatible) property type. We now keep the first field encountered for a given name, which is the one declared closest to the inspected type, so a hidden superclass field no longer overrides the subclass declaration. Closes #3500 --- .../data/core/TypeDiscoverer.java | 7 +++++-- .../data/core/TypeDiscovererUnitTests.java | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/core/TypeDiscoverer.java b/src/main/java/org/springframework/data/core/TypeDiscoverer.java index 60ca3d3576..7ebf2f7ad8 100644 --- a/src/main/java/org/springframework/data/core/TypeDiscoverer.java +++ b/src/main/java/org/springframework/data/core/TypeDiscoverer.java @@ -407,8 +407,11 @@ private Map> doGetProperties() { Map> result = new HashMap<>(); Class type = getType(); - FieldCallback callback = field -> result.put(field.getName(), - TypeInformation.of(ResolvableType.forField(field, resolvableType))); + FieldCallback callback = field -> { + if (!result.containsKey(field.getName())) { + result.put(field.getName(), TypeInformation.of(ResolvableType.forField(field, resolvableType))); + } + }; // Inspect fields first ReflectionUtils.doWithFields(type, callback); diff --git a/src/test/java/org/springframework/data/core/TypeDiscovererUnitTests.java b/src/test/java/org/springframework/data/core/TypeDiscovererUnitTests.java index 7ab688eeeb..dbbc70e21c 100755 --- a/src/test/java/org/springframework/data/core/TypeDiscovererUnitTests.java +++ b/src/test/java/org/springframework/data/core/TypeDiscovererUnitTests.java @@ -379,6 +379,14 @@ void considersNestedGenericsInEqualityForRecursiveUnresolvableTypes() throws Exc assertThat(domainType.isAssignableFrom(actualType)).isTrue(); } + @Test // GH-3500 + void superclassFieldsDoNotHideSubclassFields() { + + TypeDiscoverer discoverer = TypeDiscoverer.ofCached(ResolvableType.forClass(TheOne.class)); + + assertThat(discoverer.getProperty("theOne").getType()).isEqualTo(String.class); + } + class Person { Addresses addresses; @@ -487,6 +495,16 @@ static abstract class SomeType> { } + static class SuperclassWithProperties { + private CharSequence theOne; + private String theOther; + } + + class TheOne extends SuperclassWithProperties { + private String theOne; + } + + @SuppressWarnings("rawtypes") interface RepoWithRawGenerics extends Repository {