Docs Menu

Docs HomeDevelop ApplicationsMongoDB DriversJava

Codecs

On this page

  • Overview
  • Codec
  • CodecRegistry
  • CodecProvider
  • BsonTypeClassMap
  • Custom Codec Example

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.

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> {
@Override
public void encode(BsonWriter writer, PowerStatus value, EncoderContext encoderContext) {
if (value != null) {
writer.writeBoolean(value.equals(PowerStatus.ON) ? Boolean.TRUE : Boolean.FALSE);
}
}
@Override
public PowerStatus decode(BsonReader reader, DecoderContext decoderContext) {
return reader.readBoolean() ? PowerStatus.ON : PowerStatus.OFF;
}
@Override
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:

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, a Codec that converts Integers 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:

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() {}
@Override
@SuppressWarnings("unchecked")
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.

For more information about the classes and interfaces in this section, see the CodecProvider API Documentation.

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, refer to the BsonTypeClassMap API Documentation.

For an example of how the Document class uses BsonTypeClassMap, see the driver source code for the following classes:

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 an Integer value for which we use the IntegerCodec 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);
}
@Override
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();
}
@Override
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;
}
@Override
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() {}
@Override
@SuppressWarnings("unchecked")
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 MongoDB

  • Retrieve 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:

←  POJO CustomizationCRUD Operations →