skip to content

What is the MappingMongoConverter and how do you register a custom converter to control how a specific type is stored and read?

level: seniorimportance: should knowfreq 45%

answer

  1. MappingMongoConverter = object <-> org.bson.Document engine
  2. uses MongoMappingContext / persistence constructor / field access
  3. Converter<S,T> pair: writing -> BSON-native, reading -> domain
  4. register via MongoCustomConversions bean
  5. @WritingConverter/@ReadingConverter disambiguate direction; _class type hint

basics

~20 s

MappingMongoConverter is the component that turns your objects into BSON documents and back, using the mapping metadata. For types it can't map well, you write Converter<A,B> pairs and register them via MongoCustomConversions so they take over reading/writing that type.

solid answer

~40 s

The MappingMongoConverter is Spring Data MongoDB's object-document mapper: driven by a MongoMappingContext (MongoPersistentEntity/Property metadata), it converts domain objects to org.bson.Document on write and instantiates and populates them on read, handling ids, @Field names, nested objects, and references. When a property type isn't natively supported or you want a specific storage shape, you supply custom converters — Spring's org.springframework.core.convert.converter.Converter<S,T> implementations, one for writing (domain -> Mongo-native type like String/Document) and one for reading (back to domain). You register them by contributing a MongoCustomConversions bean (or overriding customConversions()/configureConverters in AbstractMongoClientConfiguration). Converters must map to types the driver understands. You can also use @WritingConverter/@ReadingConverter to disambiguate direction. This is how you persist things like value objects, enums as custom codes, or java.time types the way you want.

code

java · 30 lines
java
import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;

public record Money(long amountMinor, String currency) {}

@WritingConverter
class MoneyWriteConverter implements Converter<Money, Document> {
    public Document convert(Money m) {
        return new Document("amount", m.amountMinor()).append("currency", m.currency());
    }
}

@ReadingConverter
class MoneyReadConverter implements Converter<Document, Money> {
    public Money convert(Document d) {
        return new Money(d.getLong("amount"), d.getString("currency"));
    }
}

@Configuration
class MongoConfig {
    @Bean
    MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions(
            java.util.List.of(new MoneyWriteConverter(), new MoneyReadConverter()));
    }
}

go deeper

for a junior

Know that some component turns objects into BSON and that custom converters exist.

for a middle

Write a Converter pair and know they must target BSON-native types.

for a senior

Register via MongoCustomConversions, use @Writing/@ReadingConverter, and explain the _class type hint and instantiation.

for a principal

Design converter/type-mapper strategy for polymorphism, schema evolution, encryption-at-field, and consistent value-object handling across the codebase.

**What it is:** `MappingMongoConverter` (`org.springframework.data.mongodb.core.convert.MappingMongoConverter`) is the default implementation of `MongoConverter`. It is the engine that performs **object-document mapping** in both directions: - **Write:** domain object -> `org.bson.Document` (BSON) that the MongoDB driver stores. - **Read:** `org.bson.Document` -> domain object. It collaborates with the **MongoMappingContext**, which holds `MongoPersistentEntity` and `MongoPersistentProperty` metadata built from your annotations (`@Document`, `@Id`, `@Field`, `@DBRef`, etc.). Using that metadata it: resolves the `_id`, applies `@Field` key names, recurses into nested objects and collections/maps, resolves references, and **instantiates** entities. Instantiation uses an `EntityInstantiator` — it prefers a **persistence constructor** (a single or `@PersistenceCreator`-annotated constructor), can set values by **field access** (bypassing setters, even final fields via reflection/bytecode), so your objects don't need setters or a no-arg constructor. **Type conversion layer:** for individual values, `MappingMongoConverter` delegates to a `CustomConversions`/`ConversionService` stack. MongoDB's BSON supports a limited set of native types (String, numbers, boolean, Date, ObjectId, arrays, embedded documents, binary, etc.). Simple types map directly; complex objects become embedded documents. When you need a **specific representation** — e.g. store a value object as a single String, an enum as a custom code, a `Money` as `{amount, currency}`, or a legacy date format — you provide **custom converters**. **Writing custom converters:** 1. Implement Spring's `org.springframework.core.convert.converter.Converter<S, T>` twice: - a **writing** converter `Converter<DomainType, MongoNativeType>` (target must be a type the driver can store: `String`, `Date`, `org.bson.Document`, `Long`, `Binary`, etc.), - a **reading** converter `Converter<MongoNativeType, DomainType>`. 2. Optionally annotate with `@WritingConverter` / `@ReadingConverter` (`org.springframework.data.convert`) to make the direction explicit — important when the source/target types are ambiguous (e.g. both directions involve `Document`). 3. **Register** them by exposing a `MongoCustomConversions` bean: ```java @Bean MongoCustomConversions mongoCustomConversions() { return new MongoCustomConversions(List.of(new MoneyWriteConverter(), new MoneyReadConverter())); } ``` or, when extending `AbstractMongoClientConfiguration`, override `configureConverters(MongoConverterConfigurationAdapter)` / `customConversions()`. **Precedence:** custom converters take priority over the default mapping, so once registered your converter fully controls how that type is read/written. **Other MappingMongoConverter concerns:** - **`_class` type hint:** by default it writes a `_class` field storing the fully qualified class name so it can reconstruct polymorphic/abstract types on read. You can customize this via a `TypeMapper` (`DefaultMongoTypeMapper`) — e.g. remove it or use an alias — but removing it breaks polymorphic reads. - **Null handling / field naming strategy** are configurable. **Gotchas:** - A writing converter must target a **BSON-storable** type; returning an arbitrary POJO just recurses. - Forgetting `@WritingConverter`/`@ReadingConverter` when directions are ambiguous can register the converter for the wrong direction or fail. - Registering a converter for a type Spring already treats as a simple type changes it into a simple type (affecting how it's stored inside collections, etc.). - Custom converters are global for that type across all entities. **When to use:** value objects/wrapper types, enums stored as stable codes rather than names, custom temporal formats, encrypting/transforming a field, or adapting to an existing document shape.

  • What must the target type of a @WritingConverter be?
    A type the MongoDB driver can store natively — String, numbers, Boolean, java.util.Date, org.bson.Document, binary, etc. Returning an arbitrary POJO makes Spring recurse into it rather than using your intended representation.
  • What is the _class field the converter writes, and when does it matter?
    A type hint holding the fully qualified class name, written by DefaultMongoTypeMapper so polymorphic/abstract-typed properties can be reconstructed on read. It matters for inheritance; you can alias or remove it via a custom TypeMapper, but removing it breaks polymorphic deserialization.
  • Why annotate converters with @WritingConverter/@ReadingConverter?
    To disambiguate direction when the source/target types alone don't make it clear (e.g. both directions involve org.bson.Document), ensuring Spring registers each converter for the correct read or write path.

saying these in an interview costs you the question

  • Thinking a single Converter handles both read and write directions.
  • Returning a non-BSON POJO from a writing converter and expecting it to be stored as-is.
  • Not registering converters via MongoCustomConversions (annotating alone doesn't wire them).
  • Assuming entities need a no-arg constructor and setters — the converter can use a persistence constructor and field access.

context