POJO Customization
On this page
Overview
In this guide, you can learn how to define custom data conversions between
BSON and POJOs in the MongoDB Java driver. In our guide on POJOs,
we show how to specify a PojoCodecProvider
which contains classes that
provide instructions on how to convert data for one or more POJO classes
and their properties.
We show how to specify your data conversion using the ClassModel and PropertyModel classes. You can also learn about more specific customization from the section on Advanced Configuration.
We also show how to use helpers such as Conventions and Annotations to specify common serialization actions.
See the section on Discriminators if you want to serialize multiple POJO classes to documents in the same collection.
If you must implement conditional serialization or use enums, generics, interface types, or abstract types, see the section on Advanced Configuration.
If you are using only the predefined behavior to convert data between
BSON and POJOs, you can use the automatic setting for the
PojoCodecProvider
shown in the Document Data Formats: POJOs guide.
Customize a PojoCodecProvider
This section shows you how to specify your data conversion logic and POJO
classes with a PojoCodecProvider
. The PojoCodecProvider
is an
implementation of the CodecProvider
interface that specifies the
Codecs to use in data conversion. Use this implementation when performing
data conversion between BSON and POJOs.
You can create a PojoCodecProvider
instance using the
PojoCodecProvider.builder()
method. You can also chain methods to the
builder to register any of the following:
Individual POJO classes
Package names that contain POJO classes
Instances of
ClassModel
that describe conversion logic for a specific POJO class
The following example shows how you can specify the POJOs in a package named
"org.example.pojos" and add the PojoCodecProvider
to a CodecRegistry
:
import org.bson.codecs.configuration.CodecProvider; import org.bson.codecs.configuration.CodecRegistry; import org.bson.codecs.pojo.PojoCodecProvider; import static org.bson.codecs.configuration.CodecRegistries.fromRegistries; import static org.bson.codecs.configuration.CodecRegistries.fromProviders; import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry; CodecProvider pojoCodecProvider = PojoCodecProvider.builder().register("org.example.pojos").build(); CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider)); // Call withCodecRegistry(pojoCodecRegistry) on an instance of MongoClient, MongoDatabase, or MongoCollection
For more information about this class, see the PojoCodecProvider.Builder API Documentation.
ClassModel
A ClassModel
instance stores data conversion information about a specific
POJO class. It contains a list of PropertyModel
instances which describe
the property fields of the POJO, whether to convert fields, and optionally,
Codecs
to convert the fields.
A ClassModel
contains the following fields:
Field Name | Description |
---|---|
Name | The POJO class name to associate with the ClassModel . |
InstanceCreatorFactory | Contains a new instance factory that creates new instances of the POJO.
By default, it requires the POJO to have an empty constructor. |
PropertyModels | Contains a list of PropertyModel instances that specify how to
convert data to and from BSON for a field in the POJO. |
IdPropertyModelHolder | Specifies the POJO field that corresponds to the document _id field. Optional. |
Discriminator Key | Specifies the name of the discriminator field. Optional. For more information about discriminators, see the Discriminators section. |
Discriminator Value | Specifies the lookup value that represents the POJO class. Optional. For more information about discriminators, see the Discriminators section. |
Discriminator Flag | Specifies whether to serialize the discriminator, off by default. Optional. |
For more information about this class, see the ClassModel API Documentation.
To instantiate a ClassModel
, use the ClassModel.builder()
method and
specify your POJO class. The builder uses reflection to create the required
metadata.
ClassModel<Flower> classModel = ClassModel.builder(Flower.class).build();
PropertyModel
A PropertyModel
stores information on how to serialize and deserialize a
specific field in a document.
The PropertyModel
contains the following information:
Field Name | Description |
---|---|
Name | Specifies the property name in the model. |
Read Name | Name of the property to use as the key when serializing to BSON. |
Write Name | Name of the property to use as the key when deserializing from BSON. |
Type data | Contains an instance of org.bson.codecs.pojo.TypeData that
describes the data type for the field. |
Codec | Specifies a codec to use to encode or decode the field. Optional. |
Serialization checker | Determines whether to serialize a value by using the criteria
specified in the checker. |
Property accessor | Method used to access the value of the property from the POJO. |
useDiscriminator | Specifies whether to use the discriminator. For more information about discriminators, see the Discriminators section. |
To create a PropertyModel
use a PropertyModelBuilder
which you
can instantiate by calling the PropertyModel.builder()
method.
For more information about this class, see the PropertyModel.Builder API Documentation.
Conventions
The Convention
interface contains configuration options that modify the
behavior of a ClassModel
or PropertyModel
. You can specify a
Convention
in a call to PojoCodecProvider.Builder.conventions()
or to
ClassModelBuilder.conventions()
.
Note
The builders apply Convention
instances in order which can override
the behavior defined in one applied earlier.
You can access the Convention
instances defined in the BSON library
from the following static fields in the Conventions
class:
Field Name | Description |
---|---|
ANNOTATION_CONVENTION | Enables the annotations defined in the org.bson.codecs.pojo.annotations
package for your POJO. See the section on Annotations
for more information. |
CLASS_AND_PROPERTY_CONVENTION | Sets the following default values for the ClassModel and PropertyModel instances:- Discriminator key to _t - Discriminator value to the ClassModel simple type name- Id field to _id for each PropertyModel . |
DEFAULT_CONVENTIONS | Enables the following Conventions: - CLASS_AND_PROPERTY_CONVENTION - ANNOTATION_CONVENTION - OBJECT_ID_GENERATORS |
NO_CONVENTIONS | Provides an empty list. |
OBJECT_ID_GENERATORS | Adds a default IdGenerator that adds a new ObjectId for each
ClassModel that use ObjectId values in the id property. |
SET_PRIVATE_FIELDS_CONVENTION | Enables the ClassModel to set private fields using reflection
without requiring a setter method. |
USE_GETTERS_FOR_SETTERS | Enables use of getter methods as setters for Collection and Map
fields if no setter method exists. |
You can specify Conventions using one of the following methods:
To create a custom Convention, create a class that implements the
Convention
interface and override the apply()
method from which you can
access your ClassModelBuilder
instance.
Annotations
You can apply annotations to the getter and setter methods of a POJO
class. These annotations configure the ClassModel
and PropertyModel
behavior for a specific field, method, or class.
The following annotations are available from the org.bson.codecs.pojo.annotations package:
Annotation Name | Description |
---|---|
BsonCreator | Marks a public constructor or a public static method as the creator for
new instances of the class. You must annotate all parameters in the
constructor with either the BsonProperty or BsonId annotations. |
BsonDiscriminator | Specifies that a class uses a discriminator. You can set a custom
discriminator key and value. |
BsonRepresentation | Specifies the BSON type used to store the value when different from the POJO property. |
BsonId | Marks a property to serialize as the _id property. |
BsonIgnore | Marks a property to ignore. You can configure whether to serialize
and/or deserialize a property. |
BsonProperty | Specifies a custom document field name when converting the POJO field to BSON. You can include a discriminator to serialize POJOs nested within the field. ImportantWhen applying |
BsonExtraElements | Specifies the POJO field on which to deserialize all elements that are not mapped to a field. The POJO field must be one of the following types:
|
The following code snippet shows a sample POJO called Product
that uses
several of the preceding annotations.
import org.bson.BsonType; import org.bson.codecs.pojo.annotations.BsonCreator; import org.bson.codecs.pojo.annotations.BsonDiscriminator; import org.bson.codecs.pojo.annotations.BsonId; import org.bson.codecs.pojo.annotations.BsonIgnore; import org.bson.codecs.pojo.annotations.BsonProperty; import org.bson.codecs.pojo.annotations.BsonRepresentation; public class Product { private String name; private String serialNumber; private List<Product> relatedItems; public Product( String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } // ... }
Tip
When using annotations, remember to specify the
Conventions.ANNOTATION_CONVENTION
in your ClassModelBuilder
or PojoCodecProvider.Builder
. For example:
ClassModel<Product> classModel = ClassModel.builder(Product.class). conventions(Arrays.asList(Conventions.ANNOTATION_CONVENTION)).build();
The annotations in the example POJO specify the following behavior:
Reference the POJO with the specified discriminator key and value, adding the
cls
field with the value of "AnnotatedProduct" to the BSON document on write operationsConvert between the POJO
name
field and value and the BSONmodelName
field and value in the documentConvert between the POJO
serialNumber
field and value the BSON document_id
field and value in the documentOmit the
relatedItems
field and value when converting dataUse the
Product(String name)
constructor when instantiating the POJO
BsonExtraElements Example
The @BsonExtraElements
annotation allows you to specify a field to
deserialize data from a MongoDB document that lacks a corresponding POJO
field mapping. This is useful when your application needs to work with data in
a partially-defined schema. You can use this annotation to access data from
any fields that do not correspond to the fields on your POJO.
Consider a situation in which you store and retrieve data for an online store
using the Product POJO from the previous
example. As you offer a greater variety of products for the store, you
discover that you need additional fields to describe them. Instead of
mapping each additional field to the POJO, you can access them from a
single field annotated with @BsonExtraElements
as shown in the following
code example:
public class Product { private String name; private String serialNumber; private List<Product> relatedItems; private Document additionalInfo; // ...
Suppose someone added additional fields for dimensions
and weight
to
the product data such that the documents contained the following information:
{ "name": "MDB0123", "serialNumber": "62e2...", "dimensions": "3x4x5", "weight": "256g" }
The preceding document retrieved using the Product
POJO contains the
following data:
ProductWithBsonExtraElements [ name=MDB0123, serialNumber=62eb..., relatedItems=null, additionalInfo=Document{{dimensions=3x4x5, weight=256g}} ]
BsonRepresentation Error Example
The @BsonRepresentation
annotation allows you to store a POJO class field
as a different data type in your MongoDB database. The Product POJO code example in the Annotations section
of this page uses @BsonRepresentation
to store String
values as
ObjectId
values in the database documents.
However, using the @BsonRepresentation
annotation to convert between data types other
than String
and ObjectId
causes the following error message:
Codec must implement RepresentationConfigurable to support BsonRepresentation
For example, the following code adds a purchaseDate
field of type Long
to the
Product
POJO. This example attempts to use @BsonRepresentation
to represent Long
values as DateTime
values in the database:
public class Product { private String name; private String serialNumber; private Long purchaseDate; // ... }
The preceding code results in an error. Instead, you can create a custom Codec to
convert the purchaseDate
values from type Long
to DateTime
:
public class LongRepresentableCodec implements Codec<Long>, RepresentationConfigurable<Long> { private final BsonType representation; /** * Constructs a LongRepresentableCodec with a Int64 representation. */ public LongRepresentableCodec() { representation = BsonType.INT64; } private LongRepresentableCodec(final BsonType representation) { this.representation = representation; } public BsonType getRepresentation() { return representation; } public Codec<Long> withRepresentation(final BsonType representation) { if (representation != BsonType.INT64 && representation != BsonType.DATE_TIME) { throw new CodecConfigurationException(representation + " is not a supported representation for LongRepresentableCodec"); } return new LongRepresentableCodec(representation); } public void encode(final BsonWriter writer, final Long value, final EncoderContext encoderContext) { switch (representation) { case INT64: writer.writeInt64(value); break; case DATE_TIME: writer.writeDateTime(value); break; default: throw new BsonInvalidOperationException("Cannot encode a Long to a " + representation); } } public Long decode(final BsonReader reader, final DecoderContext decoderContext) { switch (representation) { case INT64: return reader.readInt64(); case DATE_TIME: return reader.readDateTime(); default: throw new CodecConfigurationException("Cannot decode " + representation + " to a Long"); } } public Class<Long> getEncoderClass() { return Long.class; } }
Then, add an instance of the LongRepresentableCodec
to your CodecRegistry
, which contains
a mapping between your Codec and the Java object type to which it applies. For instructions
on registering your custom Codec with the CodecRegistry
, see the Codecs
guide.
Discriminators
A discriminator is a property that identifies a specific document schema. The discriminator key identifies a document field to use to identify the schema. The discriminator value identifies the default value of the document field.
Use discriminators to instruct the CodecProvider
which object class to use
when deserializing to different object classes from the same collection. When
serializing the POJO to a MongoDB collection, the associated codec sets the
discriminator key-value field, unless otherwise specified in the POJO property
data.
You can set and enable a discriminator in a POJO by performing one of the following:
Use the
@BsonDiscriminator
annotation to specify the discriminator for the POJO classCall
enableDiscriminator(true)
on theClassModelBuilder
associated with the POJO class
See the following example POJO classes that contain @BsonDiscriminator
annotations and example documents that contain the discriminator fields:
public class AnonymousUser { // class code } public class RegisteredUser { // class code }
The following shows sample documents created from the preceding POJOs in a single MongoDB collection:
{ "_cls": "AnonymousUser", "_id": ObjectId("<Object ID>"), ... } { "_cls": "RegisteredUser", "_id": ObjectId("<Object ID>"), ... }
Advanced Configuration
Abstract or Interface Types in Properties
To serialize a POJO that includes abstract class or interface type properties, you must specify discriminators on the type and all its subtypes or implementations.
Suppose you defined a POJO that referenced an abstract class User
in one
of its fields as follows:
public class UserRecordPojo { private User user; // ... }
If the User
abstract class has subclasses FreeUser
and
SubscriberUser
, you can add your POJO and abstract classes to your
CodecRegistry
as follows:
ClassModel<UserRecordPojo> userRecordPojo = ClassModel.builder(UserRecordPojo.class).enableDiscriminator(true).build(); ClassModel<User> userModel = ClassModel.builder(User.class).enableDiscriminator(true).build(); ClassModel<FreeUser> freeUserModel = ClassModel.builder(FreeUser.class).enableDiscriminator(true).build(); ClassModel<SubscriberUser> subscriberUserModel = ClassModel.builder(SubscriberUser.class).enableDiscriminator(true).build(); PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder().register(userRecordPojo, userModel, freeUserModel, subscriberUserModel).build(); CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));
For more information about specifying discriminators, see the section of this guide on Discriminators.
POJOs without No-Argument Constructors
The POJO Codecs
default to calling the empty, no-argument constructor.
To specify a different constructor, you must perform the following in your
POJO:
pass the
ANNOTATION_CONVENTION
setting to yourClassModelBuilder
identify the constructor using the
BsonCreator
annotation
For an example of setting the ANNOTATION_CONVENTION
, see the
ANNOTATION_CONVENTION example.
For an example of the BsonCreator
annotation, see the
POJO with annotation code example.
Serialization Customization
By default, ClassModelBuilder
attempts to serialize all the non-null
properties in your POJO. If a property value is null
, the default
PropertySerialization
implementation skips that field.
You can customize your POJO serialization behavior by performing one of the following:
Use the
@BsonIgnore
annotation for a property to always skip serialization. Make sure to enable annotations using the appropriate Conventions.Create a custom class that overrides the
shouldSerialize()
method of thePropertySerialization
interface. Specify your custom implementation to thePropertyModelBuilder
which you can access from theClassModelBuilder
.
For more information about how to use the @BsonIgnore
annotation in a POJO,
see the section of this guide on Annotations.
The following sample code shows a custom class that implements the
PropertySerialization
interface to override the default conditions
by which to determine whether to serialize a field:
public class CourteousAgeSerialization implements PropertySerialization<Integer> { public boolean shouldSerialize(Integer value) { return (value < 30); } }
The preceding class specifies that any integer greater than 29 is not serialized, and, therefore, not included in the MongoDB document. Suppose you applied this custom serialization behavior to the following sample POJO:
public class BirthdayInvitation { private String name; private Integer age; private LocalDateTime eventDateTime; // ... }
You can specify the custom serialization by adding the
CourteousAgeSerialization
instance to the PropertyModelBuilder
from
the ClassModel
property associated with the age
field using the
following code:
ClassModelBuilder<BirthdayInvitation> classModel = ClassModel.builder(BirthdayInvitation.class); ((PropertyModelBuilder<Integer>) classModel.getProperty("age")) .propertySerialization(new CourteousAgeSerialization()); PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder().register(classModel.build()).build(); CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));
If you insert a POJO that contains a value greater than 29 in the age
field, the serialized document omits it. The POJO declaration and
resulting document resemble the following:
// constructor with parameters for name, age, and eventDateTime, respectively BirthdayInvitation invitation = new BirthdayInvitation( "Galadriel", 7582, LocalDateTime.of(2021, Month.JANUARY, 18, 30, 0) );
Since the age
field value is greater than 29, the serialized
document resembles the following:
{ "_id" : ObjectId("..."), "eventDateTime" : ..., "name" : "Galadriel" }
Generics Support
You can use the POJO Codec
to serialize classes that contain generic
properties if they meet the following criteria:
Contain only bounded concrete type parameters
If it or any of its fields are part of a class hierarchy, the top-level POJO does not contain any type parameters
The ClassModelBuilder
inspects and saves concrete type parameters to work
around type erasure. It cannot serialize classes that contain generic
properties without concrete type parameters since the JVM removes the type
parameter information.
To save type parameters, you can implement the PropertyCodecProvider
interface to specify them for generic types defined in a POJO. The following
code snippets show an example implementation of the PropertyCodecProvider
that adds serialization compatibility to the Guava Optional
class.
Suppose you wanted to serialize the following POJO with Optional
fields:
public class ApplicationUser { private Optional<Address> optionalAddress; private Optional<Subscription> optionalSubscription; // ... }
You can use the following implementation of PropertyCodecProvider
to
retrieve your custom Codec. This implementation uses the
TypeWithTypeParameters
interface to access the type information.
public class OptionalPropertyCodecProvider implements PropertyCodecProvider { public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) { // Check the main type and number of generic parameters if (Optional.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 1) { // Get the codec for the concrete type of the Optional, as its declared in the POJO. Codec<?> valueCodec = registry.get(type.getTypeParameters().get(0)); return new OptionalCodec(type.getType(), valueCodec); } else { return null; } } private static final class OptionalCodec<T> implements Codec<Optional<T>> { private final Class<Optional<T>> encoderClass; private final Codec<T> codec; private OptionalCodec(final Class<Optional<T>> encoderClass, final Codec<T> codec) { this.encoderClass = encoderClass; this.codec = codec; } public void encode(final BsonWriter writer, final Optional<T> optionalValue, final EncoderContext encoderContext) { if (optionalValue != null && optionalValue.isPresent()) { codec.encode(writer, optionalValue.get(), encoderContext); } else { writer.writeNull(); } } public Optional<T> decode(final BsonReader reader, final DecoderContext context) { return Optional.of(codec.decode(reader, context)); } public Class<Optional<T>> getEncoderClass() { return encoderClass; } } }
Register your OptionalPropertyCodecProvider
in your PojoCodecProvider
and the package that contains your POJO as follows:
CodecProvider pojoCodecProvider = PojoCodecProvider.builder() .register("org.example.pojos") .register(new OptionalPropertyCodecProvider()) .build();
For more information about the methods and classes mentioned in this section, see the following API Documentation:
For more information about generics and type parameters, see the Java language guide on Invoking and Instantiating a Generic Type.
Enum Type Support
In driver versions 4.5 and later, the PojoCodecProvider
no longer
includes a codec to convert enum
types. Ensure that you register
a codec for enum
types if you need one, such as the one in the default
codec registry.
See the documentation on the default codec registry For more information about how to register the codecs it includes.