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 need to implement conditional serialization, or if you need to use enums, generics, interface types, or abstract types, see the section on Advanced Configuration .
If you only need to use 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
ClassModelthat 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 on 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 on discriminators, see the
Discriminators
section.
|
|
Discriminator Value
|
Specifies the lookup value that represents the POJO class. Optional.
For more information on discriminators, see the
Discriminators
section.
|
|
Discriminator Flag
|
Specifies whether to serialize the discriminator, off by default. Optional.
|
For more information on 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/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 on 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 on 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 may override
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.
|
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:
TipSee also: |
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; } // ... }
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
clsfield with the value of "AnnotatedProduct" to the BSON document on write operations -
Convert between the POJO
namefield and value and the BSONmodelNamefield and value in the document -
Convert between the POJO
serialNumberfield and value the BSON document_idfield and value in the document -
Omit the
relatedItemsfield and value when converting data -
Use 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}} ]
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
@BsonDiscriminatorannotation to specify the discriminator for the POJO class -
Call
enableDiscriminator(true)on theClassModelBuilderassociated 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 on 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_CONVENTIONsetting to yourClassModelBuilder -
identify the constructor using the
BsonCreatorannotation
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
@BsonIgnoreannotation 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 thePropertySerializationinterface. Specify your custom implementation to thePropertyModelBuilderwhich is accessible from theClassModelBuilder.
For more information on 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 could look something like this:
// 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
should look something like this:
{ "_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 on 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