Frequently asked questions and solutions to common issues with MongoScala3Codec.
- General Questions
- Compilation Errors
- Runtime Issues
- Configuration Questions
- Performance Questions
- Integration Questions
A: MongoScala3Codec supports Scala 3.3.x, 3.4.x, 3.5.x, 3.6.x, and 3.7.x. It does not support Scala 2.x.
// build.sbt
scalaVersion := "3.7.1" // or any 3.3+A: No. MongoScala3Codec is built exclusively for Scala 3 using its metaprogramming features. For Scala 2, consider using:
org.mongodb.scala.bson.codecs.Macros- ReactiveMongo
- Manual codec implementations
A:
| Feature | Official Macros (Scala 2) | MongoScala3Codec |
|---|---|---|
| Scala Version | Scala 2.x | Scala 3.3+ |
| Reflection | Runtime | Compile-time |
| Sealed Traits | Limited | Full support |
| Configuration | None | Type-safe (CodecConfig) |
| None Handling | Encode as null | Configurable (Encode/Ignore) |
| Error Messages | Generic | Detailed compile-time |
| Field Path Resolution | No | Yes (MongoFieldResolver) |
A: Yes! The library:
- ✅ Has comprehensive test coverage (unit + integration)
- ✅ Uses compile-time code generation (no runtime reflection)
- ✅ Has been tested across multiple Scala 3 versions
- ✅ Follows MongoDB codec best practices
- ✅ Includes testing utilities for validation
A: MongoScala3Codec generates standard BSON codecs compatible with all MongoDB versions supported by the official Java/Scala driver. Tested with MongoDB 4.x, 5.x, and 6.x.
Problem:
[error] Cannot generate codec for type Foo
[error] No implicit Codec[Bar] found in scope
Causes & Solutions:
case class Address(city: String)
case class Person(name: String, address: Address)
// ❌ Missing Address codec
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Person]
.buildSolution: Register nested types first:
// ✅ Register Address first
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Address]
.register[Person]
.buildcase class Data(value: CustomType) // CustomType has no codecSolution: Provide a custom codec:
given customCodec: Codec[CustomType] = new Codec[CustomType] {
def encode(w: BsonWriter, v: CustomType, ctx: EncoderContext): Unit = ???
def decode(r: BsonReader, ctx: DecoderContext): CustomType = ???
def getEncoderClass: Class[CustomType] = classOf[CustomType]
}
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.withCodec(customCodec)
.register[Data]
.buildProblem:
[error] Type Person is not a case class
Cause: Trying to register a non-case class:
class Person(val name: String) // Not a case class
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Person] // ❌ Error
.buildSolution: Use case classes:
case class Person(name: String) // ✅ Case class
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Person]
.build✅ Sealed traits and classes are fully supported as of version 0.0.8!
Solution: Use registerSealed[T] for automatic polymorphic codec generation:
sealed trait Status
case class Active(since: Long) extends Status
case class Inactive(reason: String) extends Status
case class User(name: String, status: Status)
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.registerSealed[Status] // Registers Status + all subtypes
.register[User]
.build
// Works perfectly with automatic discriminator!
val user = User("Alice", Active(System.currentTimeMillis()))
// Encodes as: {"name": "Alice", "status": {"_type": "Active", "since": ...}}Key Features:
- Automatic discriminator field (default:
_type, configurable) - Single registration call for entire hierarchy
- Works with collections:
List[Status],Vector[Animal], etc. - Supports sealed trait, sealed class, and sealed abstract class
For simple enumerations without parameters, use Scala 3 enums:
enum SimpleStatus:
case Active, Inactive
case class User(status: SimpleStatus)
import io.github.mbannour.mongo.codecs.EnumValueCodecProvider
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.withProviders(EnumValueCodecProvider.forStringEnum[SimpleStatus])
.register[User]
.buildSee Sealed Trait Support Guide for comprehensive examples.
Problem:
[error] Diverging implicit expansion for type Codec[MyType]
Cause: Circular dependency in type definitions:
case class Person(friend: Person) // Self-referentialSolution: This is a limitation of compile-time derivation. Consider:
- Use
Optionto break the cycle:
case class Person(name: String, friend: Option[Person])- Use a reference by ID:
case class Person(name: String, friendId: Option[ObjectId])Problem:
[error] No given instance of type io.github.mbannour.mongo.codecs.CodecConfig was found
Cause: Using a method that requires CodecConfig without providing it.
Solution: Provide a given instance:
import io.github.mbannour.mongo.codecs.{CodecConfig, NoneHandling}
given CodecConfig = CodecConfig(noneHandling = NoneHandling.Ignore)
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.withConfig(summon[CodecConfig])
.register[User]
.buildProblem:
org.bson.codecs.configuration.CodecConfigurationException:
Can't find a codec for class com.example.User
Causes & Solutions:
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[User]
.build
// ❌ Not using the registry
val collection = database.getCollection[User]("users")Solution: Apply the registry to the collection:
// ✅ Apply registry
val collection = database
.getCollection[User]("users")
.withCodecRegistry(registry)// ❌ Generic Document collection
val collection: MongoCollection[Document] =
database.getCollection("users")Solution: Use the typed collection:
// ✅ Typed collection
val collection: MongoCollection[User] =
database.getCollection[User]("users")
.withCodecRegistry(registry)Problem:
case class User(name: String, email: Option[String])
val user = User("Alice", None)
// Stored as: {"name": "Alice", "email": null}Solution: Configure NoneHandling.Ignore:
given CodecConfig = CodecConfig(noneHandling = NoneHandling.Ignore)
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.withConfig(summon[CodecConfig])
.register[User]
.build
// Now stored as: {"name": "Alice"}Problem:
sealed trait Animal
case class Dog(name: String, breed: String) extends Animal
case class Bird(name: String, canFly: Boolean) extends Animal
case class User(_id: ObjectId, name: String, pet: Animal)
// Update a sealed-trait field using Updates.set
collection.updateOne(
Filters.eq("_id", userId),
Updates.set("pet", Bird("Tweety", true))
).toFuture()
// Later, reading it back throws:
// BsonInvalidOperationException: Missing discriminator field '_type'Cause: When Updates.set encodes a value, the MongoDB driver calls registry.get(classOf[Bird]) — the concrete subtype codec — rather than the Animal sealed trait codec. In older versions of this library, the concrete subtype codec did not write the _type discriminator, so the stored document was not self-describing and could not be decoded as an Animal.
Solution: This was fixed in the library. Ensure you are using a version that includes the fix, and register the sealed hierarchy with registerSealed[T] (not bare register[T] for individual subtypes):
// ✅ Correct - registerSealed produces discriminator-aware codecs for all subtypes
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.registerSealed[Animal] // covers Animal, Dog, Cat, Bird
.register[User]
.build
// ❌ Incorrect - bare register[T] codecs do NOT write the discriminator
val badRegistry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Dog]
.register[Bird]
.register[User]
.buildAfter the fix, Updates.set("pet", Bird("Tweety", true)) stores {"_type": "Bird", "name": "Tweety", "canFly": true}, which decodes correctly as Animal.
Problem:
case class Settings(theme: String = "light")
// Reading from DB where theme is missing
val settings = collection.find().first().toFuture()
// settings.theme is null instead of "light"Cause: The field exists in the document with a null value.
Solution: Ensure the field is completely missing from the document, or handle null values:
// Option 1: Use Option with default
case class Settings(theme: Option[String] = Some("light"))
// Option 2: Clean your data - remove null fields
import org.mongodb.scala.model.Updates
collection.updateMany(
Filters.exists("theme", false),
Updates.unset("theme")
)A: Use multiple registries or create types in separate registration blocks:
// Registry 1: Ignore None values
given config1: CodecConfig = CodecConfig(noneHandling = NoneHandling.Ignore)
val registry1 = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.withConfig(config1)
.register[User]
.build
// Registry 2: Encode None as null
given config2: CodecConfig = CodecConfig(noneHandling = NoneHandling.Encode)
val registry2 = RegistryBuilder
.from(registry1)
.withConfig(config2)
.register[Product]
.buildA: MongoScala3Codec generates code that performs comparably to hand-written codecs:
- Encoding: Within 5% of manual implementations
- Decoding: Within 5-10% of manual implementations
- Memory: No additional allocations beyond object creation
The library uses compile-time generation, so there's zero runtime reflection overhead.
A: Yes, slightly:
- Small projects (< 20 types): +1-2 seconds
- Medium projects (20-100 types): +3-5 seconds
- Large projects (> 100 types): +5-10 seconds
This is a one-time cost during compilation. Incremental compilation helps minimize the impact.
A:
- Use incremental compilation (enabled by default in sbt)
- Split large codebases into modules
- Register only types you need (don't register unused types)
- Use sbt's incremental compiler settings:
// build.sbt
ThisBuild / incOptions := {
incOptions.value.withRecompileAllFraction(0.1)
}A: Yes! MongoScala3Codec generates standard codecs compatible with reactive streams:
import org.mongodb.scala._
import akka.stream.scaladsl._
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[User]
.build
val collection = database
.getCollection[User]("users")
.withCodecRegistry(registry)
// Use with Akka Streams
Source
.fromPublisher(collection.find())
.map(user => processUser(user))
.runWith(Sink.seq)A: Yes, integrate it in your Play application:
// app/models/User.scala
case class User(name: String, email: String)
// app/dao/MongoDAO.scala
import io.github.mbannour.mongo.codecs.RegistryBuilder
import javax.inject._
import org.mongodb.scala._
@Singleton
class MongoDAO @Inject()(config: Configuration) {
private val mongoClient = MongoClient(config.get[String]("mongodb.uri"))
private val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[User]
.build
private val database = mongoClient
.getDatabase("myapp")
.withCodecRegistry(registry)
val users: MongoCollection[User] =
database.getCollection[User]("users")
}A: Yes! The MongoDB Scala driver returns Observables that can be converted:
// ZIO example
import zio._
import org.mongodb.scala._
def findUser(id: String): Task[User] = ZIO.fromFuture { implicit ec =>
collection.find(Filters.eq("_id", id)).first().toFuture()
}
// Cats Effect example
import cats.effect._
import scala.concurrent.ExecutionContext
def findUser(id: String)(implicit cs: ContextShift[IO]): IO[User] =
IO.fromFuture(IO(collection.find(Filters.eq("_id", id)).first().toFuture()))A: Use CodecTestKit for unit tests without MongoDB:
import io.github.mbannour.mongo.codecs.CodecTestKit
import org.scalatest.flatspec.AnyFlatSpec
class UserCodecSpec extends AnyFlatSpec {
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[User]
.build
given codec: Codec[User] = registry.get(classOf[User])
"User codec" should "round-trip correctly" in {
val user = User("Alice", "alice@example.com")
CodecTestKit.assertCodecSymmetry(user)
}
it should "encode to correct BSON structure" in {
val user = User("Bob", "bob@example.com")
val bson = CodecTestKit.toBsonDocument(user)
assert(bson.getString("name").getValue == "Bob")
assert(bson.getString("email").getValue == "bob@example.com")
}
}For integration tests with real MongoDB, use Testcontainers (see existing integration tests in the project).
See generated code by enabling compiler flags:
// build.sbt
scalacOptions ++= Seq(
"-Xprint:typer", // Print generated code
"-Xcheck-macros" // Validate macro expansions
)Inspect what's actually being written to MongoDB:
import io.github.mbannour.mongo.codecs.CodecTestKit
val user = User("Alice", 30)
val bson = CodecTestKit.toBsonDocument(user)
println(bson.toJson()) // See exact BSON structureCheck if your codec is registered:
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[User]
.build
val codec = registry.get(classOf[User])
println(s"Codec found: ${codec != null}")If you can't find a solution here:
- Check the examples:
/examplesdirectory in the repository - Read the integration tests:
/integration/src/test/scala - Search existing issues: GitHub Issues
- Open a new issue: Provide a minimal reproducible example
- 📖 Quickstart Guide - Get started in 5 minutes
- 🔧 Feature Overview - Learn about all features
- 🚀 Migration Guide - Migrate from other libraries