From bb48c3987b0c3728f08d738309d1ff4f7cdb4184 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 13 Feb 2026 13:23:21 +0100 Subject: [PATCH 1/2] Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index a6dc46e10e..bf6fe831ab 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.x-GH-5092-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index cab02fe276..04f93b421d 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -15,7 +15,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.x-GH-5092-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 0a758111af..513b3a453d 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.x-GH-5092-SNAPSHOT ../pom.xml From 32d1ad5152430490219cd88be820ee9446d9fbbd Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 13 Feb 2026 15:05:32 +0100 Subject: [PATCH 2/2] Allow Id conversion configuration. --- .../core/convert/MappingMongoConverter.java | 10 ++ .../mongodb/core/convert/MongoConverter.java | 102 ++++++++++++++++-- .../MappingMongoConverterUnitTests.java | 14 +++ 3 files changed, 116 insertions(+), 10 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index e7e43d9e0f..9e2ce96ed8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -173,6 +173,7 @@ public class MappingMongoConverter extends AbstractMongoConverter expressionParser); private final CachingValueExpressionEvaluatorFactory expressionEvaluatorFactory = new CachingValueExpressionEvaluatorFactory( expressionParser, this, o -> spELContext.getEvaluationContext(o)); + private ObjectIdConversion objectIdConversion = ObjectIdConversion.enforceHexStringAsObjectId(); /** * Creates a new {@link MappingMongoConverter} given the new {@link DbRefResolver} and {@link MappingContext}. @@ -303,6 +304,10 @@ public MappingContext, MongoPersistentPropert return mappingContext; } + public void setObjectIdConversion(ObjectIdConversion objectIdConversion) { + this.objectIdConversion = objectIdConversion; + } + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -333,6 +338,11 @@ public Environment getEnvironment() { return environment; } + @Override + public ObjectIdConversion objectIdConversion() { + return objectIdConversion; + } + /** * Set the {@link EntityCallbacks} instance to use when invoking * {@link org.springframework.data.mapping.callback.EntityCallback callbacks} like the {@link AfterConvertCallback}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java index dcb21ec899..f21e1df420 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java @@ -21,7 +21,6 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; import org.jspecify.annotations.Nullable; - import org.springframework.core.convert.ConversionException; import org.springframework.data.convert.CustomConversions; import org.springframework.data.convert.EntityConverter; @@ -102,7 +101,7 @@ public interface MongoConverter * @throws IllegalArgumentException if {@literal targetType} is {@literal null}. * @since 2.1 */ - @SuppressWarnings({"unchecked","NullAway"}) + @SuppressWarnings({ "unchecked", "NullAway" }) default @Nullable T mapValueToTargetType(S source, Class targetType, DbRefResolver dbRefResolver) { Assert.notNull(targetType, "TargetType must not be null"); @@ -158,6 +157,7 @@ public interface MongoConverter * @since 2.2 */ @Nullable + @SuppressWarnings({ "unchecked", "rawtypes" }) default Object convertId(@Nullable Object id, Class targetType) { if (id == null || ClassUtils.isAssignableValue(targetType, id)) { @@ -166,14 +166,10 @@ default Object convertId(@Nullable Object id, Class targetType) { if (ClassUtils.isAssignable(ObjectId.class, targetType)) { - if (id instanceof String) { - - if (ObjectId.isValid(id.toString())) { - return new ObjectId(id.toString()); - } + if (objectIdConversion().isApplicable(id)) { - // avoid ConversionException as convertToMongoType will return String anyways. - return id; + Object converted = ((ObjectIdConversion) objectIdConversion()).convert(id); + return converted != null ? converted : id; } } @@ -182,7 +178,93 @@ default Object convertId(@Nullable Object id, Class targetType) { ? getConversionService().convert(id, targetType) : convertToMongoType(id, (TypeInformation) null); } catch (ConversionException o_O) { - return convertToMongoType(id,(TypeInformation) null); + return convertToMongoType(id, (TypeInformation) null); + } + } + + /** + * @return the IdConversion to apply for {@link #convertId(Object, Class)}. A hex string to {@link ObjectId} enforcing + * conversion by default. + * @since 5.1 + */ + default ObjectIdConversion objectIdConversion() { + return ObjectIdConversion.enforceHexStringAsObjectId(); + } + + /** + * Conversion function to apply to non {@literal null} id values of {@link #sourceType()} that do not match the + * desired store id type. + * + * @param + * @since 5.1 + */ + interface ObjectIdConversion { + + /** + * Converts a {@link String} value that represent a {@link ObjectId#isValid(String) valid ObjectId} into the + * according {@link ObjectId}. + */ + ObjectIdConversion hexToString = new ObjectIdConversion<>() { + + @Override + public Class sourceType() { + return String.class; + } + + @Override + public @Nullable ObjectId convert(String source) { + + if (!ObjectId.isValid(source)) { + return null; + } + return new ObjectId(source); + } + }; + + /** + * Uses a raw {@link String} value no matter what by always returning {@literal null} for {@link #convert(Object)}. + */ + ObjectIdConversion rawString = new ObjectIdConversion<>() { + + @Override + public Class sourceType() { + return String.class; + } + + @Override + public @Nullable ObjectId convert(String source) { + return null; + } + }; + + static ObjectIdConversion enforceHexStringAsObjectId() { + return hexToString; + } + + static ObjectIdConversion retainRawString() { + return rawString; + } + + /** + * @param source must not be {@literal null}. + * @return {@literal null} to indicate (though {@link #isApplicable(Object)) that no conversion happened. + */ + @Nullable + ObjectId convert(S source); + + /** + * The source type for which a potential conversion should happen. + * + * @return must not be {@literal null}. + */ + Class sourceType(); + + default boolean isApplicable(Object probe) { + return isApplicable(probe.getClass()); + } + + default boolean isApplicable(Class probe) { + return ClassUtils.isAssignable(sourceType(), probe); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 931b9a6cea..b37401236a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -2095,6 +2095,20 @@ void convertStringIdThatIsAnObjectIdHexToObjectIdIfTargetIsObjectId() { assertThat(converter.convertId(source.toHexString(), ObjectId.class)).isEqualTo(source); } + @Test // GH-5092 + void doNotConvertStringIdThatIsAnObjectIdHexToObjectIdIfTargetIsObjectIdButConfigTellsOtherwise() { + + ObjectId source = new ObjectId(); + converter = new MappingMongoConverter(resolver, mappingContext) { + @Override + public ObjectIdConversion objectIdConversion() { + return ObjectIdConversion.retainRawString(); + } + }; + converter.afterPropertiesSet(); + assertThat(converter.convertId(source.toHexString(), ObjectId.class)).isEqualTo(source.toHexString()); + } + @Test // DATAMONGO-1798 void donNotConvertStringIdThatIsAnObjectIdHexToObjectIdIfTargetIsString() {