For AI agents: a documentation index is available at https://www.mongodb.com/docs/llms.txt — markdown versions of all pages are available by appending .md to any URL path.
Docs Menu

Encode Data with Type Codecs

In this guide, you can learn about codecs and the supporting classes that handle the encoding and decoding of Scala objects to and from BSON data in the Scala driver. The Codec abstraction allows you to map any Scala type to a corresponding BSON type. Use this to map your domain objects directly to and from BSON instead of relying on an intermediate map-based object such as Document or BsonDocument.

The Codec interface contains abstract methods for encoding and decoding Scala objects to and from BSON data. Implement these methods to define the conversion logic between BSON and your Codec implementation's Scala type.

To implement the Codec interface, define the interface's encode(), decode(), and getEncoderClass() methods. To see a code example that implements these methods, see the Basic Custom Codec Example section.

The encode() method encodes an instance of the Scala type to BSON so that the driver can store it in MongoDB. This method requires the following parameters:

Parameter Type
Description

writer

An instance of a class that implements BsonWriter, an interface that exposes methods for writing a BSON document. Use this instance to write your BSON value by using the appropriate write method for your BSON value type.

value

The data that the method encodes. The value type must match the type parameter that you assigned to your Codec implementation.

encoderContext

Metadata about the Scala object that the method encodes to BSON, including whether to store the current value in a MongoDB collection.

The encode() method doesn't return a value.

The decode() method uses the BSON data to decode an instance of the Scala type. This method requires the following parameters:

Parameter Type
Description

bsonReader

An instance of a class that implements BsonReader, an interface that exposes methods for reading a BSON document.

decoderContext

Metadata about the BSON data that the method decodes to a Scala object.

The getEncoderClass() method returns an instance of the Scala type defined in the Codec. This method provides the type information that the Java Virtual Machine (JVM) erases at runtime.

The following code samples use the PowerStatus sealed trait and PowerStatusCodec class to show how you can implement a custom Codec.

The PowerStatus sealed trait uses the case objects On and Off to represent the states of an electrical switch:

sealed trait PowerStatus
object PowerStatus {
case object On extends PowerStatus
case object Off extends PowerStatus
}

The PowerStatusCodec class implements the Codec interface to encode PowerStatus values to corresponding BSON Boolean values. The encode() method encodes a PowerStatus value to a BSON Boolean value and the decode() method decodes a BSON Boolean value to a PowerStatus value.

class PowerStatusCodec extends Codec[PowerStatus] {
override def encode(writer: BsonWriter, value: PowerStatus,
encoderContext: EncoderContext): Unit = {
if (value != null) {
writer.writeBoolean(value == PowerStatus.On)
}
}
override def decode(reader: BsonReader,
decoderContext: DecoderContext): PowerStatus = {
if (reader.readBoolean()) PowerStatus.On else PowerStatus.Off
}
override def getEncoderClass: Class[PowerStatus] = classOf[PowerStatus]
}

To use the PowerStatusCodec class, you must add an instance of the class to your CodecRegistry interface, which maps your Codec to the corresponding Scala type. The driver can encode and decode a type only if the CodecRegistry contains a Codec for that type. To learn how to include your custom Codec in your CodecRegistry interface, see the CodecRegistry section of this page.

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. To construct a CodecRegistry instance, use one of the following CodecRegistries class static factory methods. Each method builds the registry from a different source of Codec instances:

Method
Description

fromCodecs()

Builds a registry from the Codec instances that you pass to the method

fromProviders()

Builds a registry from the Codec instances that the CodecProvider instances you pass to the method supply

fromRegistries()

Builds a registry by combining the other CodecRegistry instances that you pass to the method

The following examples use two Codec implementations:

  • IntegerCodec: A Codec in the BSON package that encodes Java Integer values to BSON 32-bit integer values.

  • PowerStatusCodec: A sample Codec that decodes PowerStatus values to BSON Booleans.

The following example shows how to construct a CodecRegistry instance by using the fromCodecs() method to assign these implementations to the registry:

val codecRegistry = CodecRegistries.fromCodecs(
new IntegerCodec(), new PowerStatusCodec()
)

The following example retrieves the Codec instances in the previous example from the CodecRegistry collection:

val powerStatusCodec: Codec[PowerStatus] =
codecRegistry.get(classOf[PowerStatus])
val integerCodec: Codec[Integer] =
codecRegistry.get(classOf[Integer])

Note

If you call the get() method on a Codec instance for an unregistered class, the driver throws a CodecConfigurationException.

The default codec registry is a set of CodecProvider classes that encode between commonly used Scala and MongoDB types. The driver automatically uses the default codec registry unless you specify a custom codec registry. To learn more about the CodecProvider interface, see the CodecProvider section.

If you must override the behavior of one or more Codec classes, but want to 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, to override the default provider behavior of a Codec for a custom type with your MyEnumCodec, add it to the registry list before the default codec registry. The following example shows this pattern:

val 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:

The CodecProvider interface contains abstract methods that create Codec instances and assign them to a CodecRegistry instance. Like the CodecRegistry interface, the CodecProvider interface defines a get() method that returns Codec instances. The BSON library uses these Codec instances to encode between Scala and BSON data types.

Use a CodecProvider when you add a class to your code whose fields need corresponding Codec objects. When each field requires its own Codec instance, you must instantiate the Codec objects for each field before instantiating the Codec instance for the class. Use the CodecRegistry parameter in the get() method to pass any of the Codec instances that the Codec relies on into a constructor.

The following example shows how to implement a CodecProvider interface. The driver uses the implemented MonolightCodecProvider interface to create a MonolightCodec instance for the Monolight class.

class MonolightCodecProvider extends CodecProvider {
override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = {
if (clazz == classOf[Monolight]) {
new MonolightCodec(registry).asInstanceOf[Codec[T]]
} else {
null
}
}
}

For a complete implementation of a CodecProvider interface, including custom classes, see the Complete Custom Codec Example section of this guide.

For more information about the classes and interfaces in this section, see the following API documentation:

In this section, you can learn how to implement the Codec and CodecProvider interfaces to define the encoding and decoding logic for a custom Scala class. This section also shows how to specify and use your custom implementations to perform insert and retrieve operations.

The following code snippet shows the example custom class Monolight and its fields:

case class Monolight(
powerStatus: PowerStatus = PowerStatus.Off,
colorTemperature: Int = 0
)

The Monolight class contains the following fields, each of which requires a Codec interface implementation:

  • powerStatus describes if the light is switched on or off. The PowerStatusCodec class encodes PowerStatus values to BSON Booleans.

  • colorTemperature describes the color of the light and contains an Int value. The IntegerCodec class included in the BSON library encodes colorTemperature values to BSON 32-bit integers.

The following code example shows how to implement a Codec interface for the Monolight class. The constructor uses a CodecRegistry instance to retrieve the Codec instances it needs to encode and decode the Monolight fields.

class MonolightCodec(registry: CodecRegistry) extends Codec[Monolight] {
private val powerStatusCodec: Codec[PowerStatus] =
registry.get(classOf[PowerStatus])
private val integerCodec: Codec[Integer] =
registry.get(classOf[Integer])
override def encode(writer: BsonWriter, value: Monolight,
encoderContext: EncoderContext): Unit = {
writer.writeStartDocument()
writer.writeName("powerStatus")
powerStatusCodec.encode(writer, value.powerStatus, encoderContext)
writer.writeName("colorTemperature")
integerCodec.encode(writer, value.colorTemperature, encoderContext)
writer.writeEndDocument()
}
override def decode(reader: BsonReader,
decoderContext: DecoderContext): Monolight = {
var powerStatus: PowerStatus = PowerStatus.Off
var colorTemperature: Int = 0
reader.readStartDocument()
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
reader.readName() match {
case "powerStatus" =>
powerStatus = powerStatusCodec.decode(reader, decoderContext)
case "colorTemperature" =>
colorTemperature = integerCodec.decode(reader, decoderContext)
case "_id" =>
reader.readObjectId()
case _ =>
reader.skipValue()
}
}
reader.readEndDocument()
Monolight(powerStatus, colorTemperature)
}
override def getEncoderClass: Class[Monolight] = classOf[Monolight]
}

The following code example shows how to construct Codec instances for the fields in the Monolight class and implement a custom CodecProvider:

class MonolightCodecProvider extends CodecProvider {
override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = {
if (clazz == classOf[Monolight]) {
new MonolightCodec(registry).asInstanceOf[Codec[T]]
} else {
null
}
}
}

The get() method returns a new MonolightCodec when the driver requests a Codec for the Monolight class. The method passes the CodecRegistry to the MonolightCodec constructor so the MonolightCodec can retrieve the Codec instances for its fields, such as PowerStatusCodec and IntegerCodec. If the driver requests a Codec for any other class, the method returns null.

After you define the conversion logic, you can perform the following operations:

  • Store data from instances of the Monolight class into MongoDB

  • Retrieve data from MongoDB into instances of the Monolight class

The following example assigns the MonolightCodecProvider class to the MongoCollection instance by passing it to the withCodecRegistry() method. The example then inserts a new Monolight instance into the collection by calling the insertOne() method and then calling the find() method to return the stored Monolight instance. The output shows the retrieved Monolight instance, which confirms that the custom codecs successfully encoded and decoded the data.

object MonolightCodecExample {
def main(args: Array[String]): Unit = {
val uri = "<connection string URI>"
val mongoClient = MongoClient(uri)
val codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(
new IntegerCodec(), new PowerStatusCodec()
),
CodecRegistries.fromProviders(new MonolightCodecProvider()),
MongoClientSettings.getDefaultCodecRegistry()
)
val database = mongoClient.getDatabase("codecs_example_products")
val collection: MongoCollection[Monolight] = database
.getCollection[Monolight]("monolights")
.withCodecRegistry(codecRegistry)
val myMonolight = Monolight(PowerStatus.On, 5200)
Await.result(
collection.insertOne(myMonolight).toFuture(),
Duration.Inf
)
val lights = Await.result(
collection.find().toFuture(),
Duration.Inf
)
println(lights)
mongoClient.close()
}
}
List(Monolight(On,5200))

For more information about the methods and classes mentioned in this section, see the following API documentation: