Codecs
On this page
Overview
In this guide, you can learn about Codecs and the supporting classes that
handle the encoding and decoding of Java objects to and from BSON data
in the MongoDB Java driver. The Codec
abstraction allows you to map any Java type to
a corresponding BSON type. You can use this to map your domain objects
directly to and from BSON instead of using an intermediate map-based object such
as Document
or BsonDocument
.
You can learn how to specify custom encoding and decoding logic using
the Codec
abstraction and view example implementations in the following
sections:
If you are customizing encoding and decoding logic for plain old Java objects (POJOs), read our guide on POJO Customization.
Codec
The Codec
interface contains abstract methods for serializing and
deserializing Java objects to BSON data. You can define your conversion logic
between BSON and your Java object in your implementation of this interface.
To implement the Codec
interface, override the encode()
, decode()
,
and getEncoderClass()
abstract methods.
The encode()
method requires the following parameters:
Parameter Type | Description |
---|---|
writer | An instance of a class that implements BsonWriter , an interface type
that exposes methods for writing a BSON document. For example, the
BsonBinaryWriter implementation writes to a binary stream of data.
Use this instance to write your BSON value using the appropriate
write method. |
value | The data that your implementation encodes. The type must match the type
variable assigned to your implementation. |
encoderContext | Contains meta information about the Java object data that it encodes
to BSON including whether to store the current value in a
MongoDB collection. |
This method uses the BsonWriter
instance to send the encoded value to
MongoDB and does not return a value.
The decode()
method returns your Java object instance populated with the
value from the BSON data. This method requires the following parameters:
Parameter Type | Description |
---|---|
bsonReader | An instance of a class that implements BsonReader , an interface type
that exposes methods for reading a BSON document. For example, the
BsonBinaryReader implementation reads from a binary stream of data. |
decoderContext | Contains information about the BSON data that it decodes to a Java
object. |
The getEncoderClass()
method returns a class instance of the Java class
since Java cannot infer the type due to type erasure.
See the following code examples that show how you can implement a custom
Codec
.
The PowerStatus
enum contains the values "ON" and "OFF" to represent
the states of an electrical switch.
public enum PowerStatus { ON, OFF }
The PowerStatusCodec
class implements Codec
in order to convert
the Java enum
values to corresponding BSON boolean values. The
encode()
method converts a PowerStatus
to a BSON boolean and the
decode()
method performs the conversion in the opposite direction.
public class PowerStatusCodec implements Codec<PowerStatus> { public void encode(BsonWriter writer, PowerStatus value, EncoderContext encoderContext) { if (value != null) { writer.writeBoolean(value.equals(PowerStatus.ON) ? Boolean.TRUE : Boolean.FALSE); } } public PowerStatus decode(BsonReader reader, DecoderContext decoderContext) { return reader.readBoolean() ? PowerStatus.ON : PowerStatus.OFF; } public Class<PowerStatus> getEncoderClass() { return PowerStatus.class; } }
You can add an instance of the PowerStatusCodec
to your CodecRegistry
which contains a mapping between your Codec
and the Java object type to
which it applies. Continue to the CodecRegistry
section of this page to see how you can include your Codec
.
For more information about the classes and interfaces in this section, see the following API Documentation:
CodecRegistry
A CodecRegistry
is an immutable collection of Codec
instances that
encode and decode the Java classes they specify. You can use any of the
following CodecRegistries
class static factory methods to construct a
CodecRegistry
from the Codec
instances contained in the associated
types:
fromCodecs()
fromProviders()
fromRegistries()
The following code snippet shows how to construct a CodecRegistry
using
the fromCodecs()
method:
CodecRegistry codecRegistry = CodecRegistries.fromCodecs(new IntegerCodec(), new PowerStatusCodec());
In the preceding example, we assign the CodecRegistry
the following Codec
implementations:
IntegerCodec
, aCodec
that convertsIntegers
and is part of the BSON package.PowerStatusCodec, our sample
Codec
that converts certain Java strings to BSON booleans.
You can retrieve the Codec
instances from the CodecRegistry
instance
from the prior example using the following code:
Codec<String> powerStatusCodec = codecRegistry.get(String.class); Codec<Integer> integerCodec = codecRegistry.get(Integer.class);
If you attempt to retrieve a Codec
instance for a class that is not
registered, the get()
method throws a CodecConfigurationException
exception.
For more information about the classes and interfaces in this section, see the following API Documentation:
CodecProvider
A CodecProvider
is an interface that contains abstract methods that create
Codec
instances and assign them to a CodecRegistry
instance. Similar
to the CodecRegistry
, the BSON library uses the Codec
instances
retrieved by the get()
method to convert between Java and BSON data types.
However, in cases in which you add a class that contains fields that require
corresponding Codec
objects, you need to ensure that you instantiate the
Codec
objects for the class fields before you instantiate the
Codec
for the class. You can use the CodecRegistry
parameter in
the get()
method to pass any of the Codec
instances that the
Codec
relies on.
The following code example shows how you can implement CodecProvider
to
pass the MonolightCodec
any Codec
instances it needs in a
CodecRegistry
instance such as the PowerStatusCodec
from our prior
example:
public class MonolightCodecProvider implements CodecProvider { public MonolightCodecProvider() {} public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) { if (clazz == Monolight.class) { return (Codec<T>) new MonolightCodec(registry); } // return null when not a provider for the requested class return null; } }
To see a runnable example that demonstrates read and write operations using
these Codec
classes, see the Custom Codec Example
section of this guide.
When working with POJOs, consider using the PojoCodecProvider
to
minimize duplicate code to convert commonly-used data types and customize
their behavior. See our
POJO Customization guide
for more information.
Default Codec Registry
The default codec registry is a set of CodecProvider
classes that
specify conversion between commonly-used Java and MongoDB types. The
driver automatically uses the default codec registry unless you specify
a different one.
If you need to override the behavior of one or more Codec
classes, but
keep the behavior from the default codec registry for the other classes,
you can specify all of the registries in order of precedence. For example,
suppose you wanted to override the default provider behavior of a Codec
for
enum types with your custom MyEnumCodec
, you must add it to the registry
list prior to the default codec registry as shown in the example below:
CodecRegistry newRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new MyEnumCodec()), MongoClientSettings.getDefaultCodecRegistry());
For more information about the classes and interfaces in this section, see the following API documentation sections:
BsonTypeClassMap
The BsonTypeClassMap
class contains a recommended mapping between BSON
and Java types. You can use this class in your custom Codec
or
CodecProvider
to help you manage which Java types to decode your BSON
types to container classes that implement Iterable
or Map
such as
the Document
class.
You can add or modify the BsonTypeClassMap
default mapping by passing a
Map
containing new or replacement entries.
The following code snippet shows how you can retrieve the Java class type
that corresponds to the BSON type in the default BsonTypeClassMap
instance:
BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(); Class<?> clazz = bsonTypeClassMap.get(BsonType.ARRAY); System.out.println("Java type: " + clazz.getName());
This code outputs the following:
Java type: java.util.List
You can modify these mappings in your instance by specifying replacements in the
BsonTypeClassMap
constructor. The following code snippet shows how
you can replace the mapping for ARRAY
in your BsonTypeClassMap
instance with the Set
class:
Map<BsonType, Class<?>> replacements = new HashMap<BsonType, Class<?>>(); replacements.put(BsonType.ARRAY, Set.class); BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(replacements); Class<?> clazz = bsonTypeClassMap.get(BsonType.ARRAY); System.out.println("Java type: " + clazz.getName());
This code outputs the following:
Java type: java.util.Set
For a complete list of the default mappings, see the BsonTypeClassMap API Documentation.
For an example of how the Document
class uses BsonTypeClassMap
, see
the driver source code for the following classes:
Custom Codec Example
In this section, we show how you can implement Codec
and CodecProvider
to define the encoding and decoding logic for a custom Java class. We also show
how you can specify and use your custom implementations to perform insert
and retrieve operations.
The following code snippet shows our example custom class called Monolight
and its fields that we want to store and retrieve from a MongoDB collection:
public class Monolight { private PowerStatus powerStatus = PowerStatus.OFF; private Integer colorTemperature; public Monolight() {} // ...
This class contains the following fields, each of which we need to assign a
Codec
:
powerStatus
describes whether the light is switched "on" or "off" for which we use the PowerStatusCodec that converts specific enum values to BSON booleans.colorTemperature
describes the color of the light and contains anInteger
value for which we use theIntegerCodec
included in the BSON library.
The following code example shows how we can implement a Codec
for the
Monolight
class. Note that the constructor expects an instance of
CodecRegistry
from which it retrieves the Codec
instances it needs
to encode and decode its fields:
public class MonolightCodec implements Codec<Monolight>{ private Codec<PowerStatus> powerStatusCodec; private Codec<Integer> integerCodec; public MonolightCodec(CodecRegistry registry) { this.powerStatusCodec = registry.get(PowerStatus.class); this.integerCodec = registry.get(Integer.class); } // Defines an encode() method to convert Monolight enum values to BSON values public void encode(BsonWriter writer, Monolight value, EncoderContext encoderContext) { writer.writeStartDocument(); writer.writeName("powerStatus"); powerStatusCodec.encode(writer, value.getPowerStatus(), encoderContext); writer.writeName("colorTemperature"); integerCodec.encode(writer, value.getColorTemperature(), encoderContext); writer.writeEndDocument(); } // Defines a decode() method to convert BSON values to Monolight enum values public Monolight decode(BsonReader reader, DecoderContext decoderContext) { Monolight monolight = new Monolight(); reader.readStartDocument(); while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) { String fieldName = reader.readName(); if (fieldName.equals("powerStatus")) { monolight.setPowerStatus(powerStatusCodec.decode(reader, decoderContext)); } else if (fieldName.equals("colorTemperature")) { monolight.setColorTemperature(integerCodec.decode(reader, decoderContext)); } else if (fieldName.equals("_id")){ reader.readObjectId(); } } reader.readEndDocument(); return monolight; } // Returns an instance of the Monolight class, since Java cannot infer the class type public Class<Monolight> getEncoderClass() { return Monolight.class; } }
To ensure we make the Codec
instances for the fields available for
Monolight
, we implement a custom CodecProvider
shown in the following
code example:
public class MonolightCodecProvider implements CodecProvider { public MonolightCodecProvider() {} public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) { if (clazz == Monolight.class) { return (Codec<T>) new MonolightCodec(registry); } // return null when not a provider for the requested class return null; } }
After defining the conversion logic, we can perform the following:
Store data from instances of
Monolight
into MongoDBRetrieve data from MongoDB into instances of
Monolight
The following example class contains code that assigns the
MonolightCodecProvider
to the MongoCollection
instance by passing it
to the withCodecRegistry()
method. The example class also inserts and
retrieves data using the Monolight
class and associated codecs:
public class MonolightCodecExample { public static void main(String[] args) { String uri = "<MongoDB connection URI>"; try (MongoClient mongoClient = MongoClients.create(uri)) { CodecRegistry codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new IntegerCodec(), new PowerStatusCodec()), CodecRegistries.fromProviders(new MonolightCodecProvider()), MongoClientSettings.getDefaultCodecRegistry()); MongoDatabase database = mongoClient.getDatabase("codecs_example_products"); MongoCollection<Monolight> collection = database.getCollection("monolights", Monolight.class).withCodecRegistry(codecRegistry); // construct and insert an instance of Monolight Monolight myMonolight = new Monolight(); myMonolight.setPowerStatus(PowerStatus.ON); myMonolight.setColorTemperature(5200); collection.insertOne(myMonolight); // retrieve one or more instances of Monolight List<Monolight> lights = new ArrayList<>(); collection.find().into(lights); System.out.println(lights); } } }
If you run the preceding example, you should see the following output:
[Monolight [powerStatus=ON, colorTemperature=5200]]
For more information about the methods and classes mentioned in this section, see the following API Documentation: