From 29a692852e3c0cf3a5b18bbe6b4d192aac0767e5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 28 Jul 2026 11:45:38 +0200 Subject: [PATCH 1/3] Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2ace266bf9..e3ad2b9562 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-3515-SNAPSHOT Spring Data Core Core Spring concepts underpinning every Spring Data module. From d074578735bd4f0e69e5aceb3e2bde6bb06bbe1c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 28 Jul 2026 11:51:40 +0200 Subject: [PATCH 2/3] Leniently resolve entities during property path traversal. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now rely on MappingContext.getPersistentEntity(…) for persistent entity resolution instead of validating PersistentProperty.isEntity(…) ourselves. isEntity considers key and value types when using Map types whereas getPersistentEntity(…) only considers the value type for property path traversal. This difference in behavior caused MappingException during property path traversal amplified by eager auditing metadata resolution. Additionally, we leniently resolve a backing persistent entity when obtaining property paths from a collection or map type as MappingContext contains registrations for Map-based entity types that resolve to a raw Map type erasing type parameters. --- .../PersistentPropertyPathFactory.java | 35 ++++++++++++++----- ...gAuditableBeanWrapperFactoryUnitTests.java | 20 +++++++++++ ...ersistentPropertyPathFactoryUnitTests.java | 26 ++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java b/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java index b377ad524b..a639782e05 100644 --- a/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java +++ b/src/main/java/org/springframework/data/mapping/context/PersistentPropertyPathFactory.java @@ -15,8 +15,16 @@ */ package org.springframework.data.mapping.context; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -49,8 +57,6 @@ */ class PersistentPropertyPathFactory, P extends PersistentProperty

> { - private static final Predicate>> IS_ENTITY = PersistentProperty::isEntity; - private final ConcurrentLruCache propertyPaths = new ConcurrentLruCache<>(512, it -> createPersistentPropertyPath(it.path(), it.type())); private final MappingContext context; @@ -228,7 +234,18 @@ private Collection> from(TypeInformation type, return Collections.emptyList(); } - return from(context.getRequiredPersistentEntity(actualType), filter, traversalGuard, basePath); + E entity; + if (type.isCollectionLike() || type.isMap()) { + entity = context.getPersistentEntity(actualType); + } else { + entity = context.getRequiredPersistentEntity(actualType); + } + + if (entity == null) { + return Collections.emptyList(); + } + + return from(entity, filter, traversalGuard, basePath); } private Collection> from(E entity, Predicate filter, Predicate

traversalGuard, @@ -251,9 +268,11 @@ private Collection> from(E entity, Predicate true); + + var factory = new MappingAuditableBeanWrapperFactory(PersistentEntities.of(context)); + + assertThat(factory.getBeanWrapperFor(new WithNestedMap())).isNotNull(); + } + @Test // DATACMNS-365 void discoversAuditingPropertyOnField() { @@ -406,4 +418,12 @@ static class WithEmbedded { Collection embeddeds; Map embeddedMap; } + + static class Cell { + Object value; + } + + static class WithNestedMap { + Map> values; + } } diff --git a/src/test/java/org/springframework/data/mapping/context/PersistentPropertyPathFactoryUnitTests.java b/src/test/java/org/springframework/data/mapping/context/PersistentPropertyPathFactoryUnitTests.java index eb56b73efe..24dc829389 100644 --- a/src/test/java/org/springframework/data/mapping/context/PersistentPropertyPathFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/context/PersistentPropertyPathFactoryUnitTests.java @@ -41,6 +41,7 @@ * * @author Oliver Gierke * @author Christoph Strobl + * @author Mark Paluch * @soundtrack Cypress Hill - Illusions (Q-Tip Remix, Unreleased & Revamped) */ class PersistentPropertyPathFactoryUnitTests { @@ -176,6 +177,22 @@ void returnsShortestPathFirst() { .hasValueSatisfying(it -> assertThat(it.toDotPath()).isEqualTo("third.lastname")); } + @Test // GH-3515 + void returnsPathsForMapWithEntityKeyAndSimpleValueType() { + + var paths = factory.from(WithEntityKeyMap.class, it -> true); + + assertThat(paths).extracting(PersistentPropertyPath::toDotPath).containsExactly("map"); + } + + @Test // GH-3515 + void returnsPathsForRawTypeComplexBound() { + + var paths = factory.from(BoundedGeneric.class, it -> true); + + assertThat(paths).extracting(PersistentPropertyPath::toDotPath).containsExactly("map"); + } + static class PersonSample { List persons; } @@ -222,4 +239,13 @@ static class Second { static class Third { String lastname; } + + static class WithEntityKeyMap { + Map map; + } + + static class BoundedGeneric { + Map map; + } + } From 52ae6ee6d656e4a5b12eabe39d646b1478bfab55 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 28 Jul 2026 11:52:23 +0200 Subject: [PATCH 3/3] Polishing. Fix Javadoc to reflect API behavior. --- .../data/mapping/context/MappingContext.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/springframework/data/mapping/context/MappingContext.java b/src/main/java/org/springframework/data/mapping/context/MappingContext.java index 1fe20a5677..eedf1dbb6f 100644 --- a/src/main/java/org/springframework/data/mapping/context/MappingContext.java +++ b/src/main/java/org/springframework/data/mapping/context/MappingContext.java @@ -134,14 +134,13 @@ default E getRequiredPersistentEntity(TypeInformation type) throws MappingExc E getPersistentEntity(P persistentProperty); /** - * Returns the {@link PersistentEntity} mapped by the given {@link PersistentProperty}. + * Returns the {@link PersistentEntity} mapped by the given {@link PersistentProperty}. Will throw + * {@link MappingException} for types that are considered simple ones. * * @param persistentProperty must not be {@literal null}. - * @return the {@link PersistentEntity} mapped by the given {@link PersistentProperty} or {@literal null} if no - * {@link PersistentEntity} exists for it or the {@link PersistentProperty} does not refer to an entity (the - * type of the property is considered simple see - * {@link org.springframework.data.mapping.model.SimpleTypeHolder#isSimpleType(Class)}). + * @return the {@link PersistentEntity} mapped by the given {@link PersistentProperty}. * @throws MappingException when no {@link PersistentEntity} can be found for given {@link PersistentProperty}. + * @see #getPersistentEntity(PersistentProperty) */ default E getRequiredPersistentEntity(P persistentProperty) throws MappingException {