MongoScala3Codec β Compileβtime BSON codecs for Scala 3. Auto-generates type-safe BSON codecs at compile time with zero runtime overhead and production-ready error handling.
- Quick Start
- Why MongoScala3Codec?
- Installation
- Features
- Documentation
- Usage Examples
- Architecture
- Performance
- Contributing
- Support & Community
- License
1. Add dependency to build.sbt:
libraryDependencies ++= Seq(
"io.github.mbannour" %% "mongoscala3codec" % "0.0.8",
("org.mongodb.scala" %% "mongo-scala-driver" % "5.6.0").cross(CrossVersion.for3Use2_13)
)2. Copy, paste, and run:
import org.bson.types.ObjectId
import io.github.mbannour.mongo.codecs.{RegistryBuilder, CodecConfig, NoneHandling}
import org.mongodb.scala.MongoClient
case class Address(street: String, city: String, zipCode: Int)
case class Person(_id: ObjectId, name: String, address: Address, email: Option[String])
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.ignoreNone
.registerAll[(Address, Person)]
.build
val mongoClient = MongoClient("mongodb://localhost:27017")
val database = mongoClient.getDatabase("myapp").withCodecRegistry(registry)
val people = database.getCollection[Person]("people")
val person = Person(new ObjectId(), "Alice", Address("123 Main", "NYC", 10001), Some("[email protected]"))
people.insertOne(person).toFuture()That's it! No manual codec writing, no reflection, no runtime overhead.
π New to the library? Continue with Quickstart Guide for detailed walkthrough.
This library was created to enable native MongoDB usage in Scala 3. The official mongo-scala-driver only supports Scala 2.11, 2.12, and 2.13 because it relies heavily on Scala 2 macros for automatic codec generation. Since Scala 3 completely redesigned the macro system, the official driver requires a major rewrite to support Scala 3.
Your options without MongoScala3Codec:
- β¬οΈ Downgrade to Scala 2.13 (lose Scala 3 features)
- β Wait indefinitely for official Scala 3 support
- βοΈ Write manual codecs for every type (100+ lines per case class)
| Feature | MongoScala3Codec | mongo-scala-driver | ReactiveMongo |
|---|---|---|---|
| Scala 3 Support | β Native | β Scala 2 onlyΒΉ | β Scala 2 onlyΒ² |
| Macro System | β Scala 3 macros | β Scala 2 macrosΒ³ | |
| Compile-Time Codecs | β Zero overhead | β Scala 2 only | |
| Type-Safe Field Paths | β MongoPathβ΅ | β | β |
| None Handling Options | β Ignore/Encode | β Ignore/Encode | β |
| Production Error Messages | β DetailedβΆ |
Footnotes:
- mongo-scala-driver supports Scala 2.11, 2.12, 2.13 only - Scaladex
- ReactiveMongo v0.20.13 supports Scala 2.11, 2.12, 2.13 only - Scaladex
- mongo-scala-driver "heavily uses macros which were dropped in Scala 3" - Stack Overflow
- ReactiveMongo uses both compile-time macros and runtime reflection components
- Compile-time safe field paths:
MongoPath.of[User](_.address.?.city)respects@BsonProperty - Enhanced macro errors with β/β examples, runtime errors with causes and suggestions
Bottom line: MongoScala3Codec is the only library that enables native MongoDB usage in Scala 3 with compile-time safety and BSON-native types.
Add to your build.sbt:
libraryDependencies += "io.github.mbannour" %% "mongoscala3codec" % "0.0.8"For use with MongoDB Scala Driver:
libraryDependencies ++= Seq(
"io.github.mbannour" %% "mongoscala3codec" % "0.0.8",
("org.mongodb.scala" %% "mongo-scala-driver" % "5.6.0").cross(CrossVersion.for3Use2_13)
)Requirements:
- Scala 3.3.1 or higher
- JDK 11 or higher
- β Zero Boilerplate - One line registers any case class
- β Compile-Time Safe - Catch errors before deployment, not in production
- β Zero Runtime Overhead - Codecs generated at compile time, no reflection
- β
Type-Safe Field Paths -
MongoPath.of[User](_.address.?.city)- unique in Scala - β BSON-Native - Preserves ObjectId, Binary, Decimal128, Dates
- β Production-Ready - Comprehensive error messages, 280+ tests, stress-tested
- β Sealed Traits/Classes - Polymorphic codecs with automatic discriminators (v0.0.8)
- β Scala 3 Enums - Full support with string/ordinal/custom field encoding
- β Default Parameters - Missing fields use defaults automatically
- β
Options & Nested Types -
Option[T], nested case classes, collections - β Opaque Types - Zero-cost wrappers work seamlessly
- β Primitive Types - Complete coverage (Byte, Short, Char, Int, Long, Float, Double, Boolean, String)
- β UUID & Binary Types - Built-in support for common data types
- β Collections - List, Set, Vector, Map with proper BSON encoding
- β Flexible None Handling - Encode as null or omit from document
- β
Custom Field Names -
@BsonPropertyannotations - β
Batch Registration -
registerAll[(A, B, C)]for optimal compile times - β
Conditional Registration -
registerIf[T](condition)for environment-specific codecs - β
Testing Utilities -
CodecTestKitfor round-trip validation - β
Type-Safe Configuration - Immutable
CodecConfigwith builder pattern
π Complete Documentation Index - Navigation hub for all documentation
| I want to... | Documentation |
|---|---|
| Get started quickly | Quickstart Guide |
| Understand all features | Feature Overview |
| Work with sealed traits | Sealed Trait Support |
| Use Scala 3 enums | Enum Support |
| Understand BSON mapping | BSON Type Mapping |
| Fix compilation errors | FAQ & Troubleshooting |
| Integrate with MongoDB | MongoDB Interop |
| Migrate from another library | Migration Guide |
| Understand internals | How It Works |
val reg = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[MyType]
.buildval reg = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.registerAll[(Address, Person, Task)]
.buildTip: Prefer registerAll[(A, B, C)] over sequential register calls for better compile-time performance.
val reg = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.ignoreNone // Omit None fields, or use .encodeNone to encode as null
.registerAll[(Address, Person)]
.build| Setting | BSON Result |
|---|---|
NoneHandling.Encode (.encodeNone) |
Encodes None as null |
NoneHandling.Ignore (.ignoreNone) |
Omits the field entirely |
val isProd = sys.env.get("APP_ENV").contains("prod")
val reg = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[CommonType]
.registerIf[ProdOnlyType](isProd)
.buildval common = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Address]
.register[Person]
val extra = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Department]
val reg = (common ++ extra).buildβ
Sealed traits and classes are fully supported! Use registerSealed[T] for automatic polymorphic codec generation:
sealed trait Animal
case class Dog(name: String, breed: String) extends Animal
case class Cat(name: String, lives: Int) extends Animal
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.registerSealed[Animal] // Registers Animal + all subtypes
.build
// Works polymorphically with automatic discriminator field
val animals: List[Animal] = List(
Dog("Rex", "Labrador"),
Cat("Whiskers", 9)
)
database.getCollection[Animal]("animals").insertMany(animals).toFuture()Batch Registration:
// Register multiple sealed traits efficiently
val registry = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.registerSealedAll[(Animal, Vehicle, Status)]
.buildFeatures:
- β
Automatic discriminator field (default:
_type, configurable) - β Single call registers entire hierarchy
- β Works with collections, nested structures, and Option fields
- β Supports sealed trait, sealed class, and sealed abstract class
Limitations:
β οΈ Case objects in sealed hierarchies are not supported - use case classes or Scala 3 enumsβ οΈ Sealed traits with type parameters are not yet supported
π See Sealed Trait Support Guide for comprehensive examples.
Scala 3 enums are supported via EnumValueCodecProvider.
import io.github.mbannour.mongo.codecs.EnumValueCodecProvider
import org.bson.codecs.configuration.CodecRegistries.{fromProviders, fromRegistries}
import org.mongodb.scala.MongoClient
enum Priority:
case Low, Medium, High
val base = fromRegistries(
MongoClient.DEFAULT_CODEC_REGISTRY,
fromProviders(EnumValueCodecProvider.forStringEnum[Priority])
)
val reg = RegistryBuilder
.from(base)
.register[Task]
.buildEnum Variants:
forStringEnum[E]β stores enum by its name (stable, readable)forOrdinalEnum[E]β stores enum by its ordinal (compact, renumbering-sensitive)
Best practice: Prefer string-based enums (forStringEnum) for schema stability and readability.
π See Enum Support Guide for advanced patterns.
Avoid stringly-typed bugs in filters, updates, projections, and sorts with MongoPath.
import io.github.mbannour.fields.MongoPath
import io.github.mbannour.fields.MongoPath.syntax.? // Option hop
import org.mongodb.scala.model.Filters
import org.mongodb.scala.bson.annotations.BsonProperty
import org.bson.types.ObjectId
case class Address(street: String, @BsonProperty("zip") zipCode: Int)
case class User(_id: ObjectId, name: String, address: Option[Address])
val zipPath = MongoPath.of[User](_.address.?.zipCode) // "address.zip"
val filter = Filters.equal(zipPath, 12345)
val idPath = MongoPath.of[User](_._id) // "_id"
val namePath = MongoPath.of[User](_.name) // "name"Rules:
- Use simple access chains, e.g.
_.a.b.c - Import
MongoPath.syntax.?to transparently traverseOption @BsonPropertyvalues on constructor params are respected
Opaque types work out of the box (zero runtime overhead).
object Domain:
opaque type UserId = String
object UserId:
def apply(v: String): UserId = v
extension (u: UserId) def value: String = u
import Domain.*
import org.bson.types.ObjectId
case class Profile(_id: ObjectId, userId: UserId, age: Int)
val reg = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[Profile]
.buildTest your codecs without a database using CodecTestKit:
import io.github.mbannour.mongo.codecs.{CodecTestKit, RegistryBuilder, CodecConfig, NoneHandling}
import org.bson.codecs.Codec
import org.bson.types.ObjectId
import org.mongodb.scala.MongoClient
case class User(_id: ObjectId, name: String, email: Option[String])
given CodecConfig = CodecConfig(noneHandling = NoneHandling.Ignore)
val reg = RegistryBuilder
.from(MongoClient.DEFAULT_CODEC_REGISTRY)
.register[User]
.build
given Codec[User] = reg.get(classOf[User])
// Round-trip symmetry
CodecTestKit.assertCodecSymmetry(User(new ObjectId(), "Alice", Some("[email protected]")))
// Inspect BSON
val bson = CodecTestKit.toBsonDocument(User(new ObjectId(), "Bob", None))
println(bson.toJson()) // email omitted due to IgnoreWhy Use It?
β Catch codec bugs early (no DB needed) β Validate BSON structure deterministically β Works with ScalaTest, MUnit, ScalaCheck
MongoScala3Codec leverages Scala 3's inline macros and metaprogramming for compile-time codec generation:
βββββββββββββββββββββββ
β Scala 3 Case Class β
β case class User β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Inline Macros β
β (Compile-Time) β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β BSON Codec β
β (Zero Overhead) β
ββββββββββββ¬βββββββββββ
β
βΌ
βββββββββββββββββββββββ
β MongoDB Driver β
β BsonDocument β
βββββββββββββββββββββββ
Key Design Principles:
- Compile-Time Generation - All codec logic generated at compile time, no runtime reflection
- Type Safety - Invalid configurations caught by compiler, not at runtime
- Zero Overhead - Generated code is as efficient as hand-written codecs
- Incremental Compilation - Smart caching reduces recompilation time
- Clear Error Messages - Actionable compile-time and runtime diagnostics
π See How It Works for detailed architecture explanation.
MongoScala3Codec includes JMH microbenchmarks for measuring codec performance. The benchmarks cover:
- Flat case classes with primitives
- Nested structures with
Optionfields - Sealed trait hierarchies with discriminators
- Large collections (List, Vector, Map)
Headline Results:
- Encode Performance: ~500,000 ops/sec for simple case classes
- Decode Performance: ~400,000 ops/sec for simple case classes
- Memory: Zero allocation overhead vs hand-written codecs
- Compile Time: Batch registration reduces compile time by 60%
π See Benchmarks Documentation for details on running benchmarks and interpreting results.
Contributions are welcome! We appreciate:
- π Bug reports and fixes
- π Documentation improvements
- β¨ Feature implementations
- π§ͺ Test coverage enhancements
- π‘ Ideas and feedback
Before contributing:
- Check existing issues for duplicates
- Read CONTRIBUTING.md for guidelines
- Follow the Code of Conduct
Development Setup:
git clone https://github.com/mbannour/MongoScala3Codec.git
cd MongoScala3Codec
sbt compile
sbt test- GitHub Discussions - Q&A, ideas, and general discussion
- Issue Tracker - Bug reports and feature requests
- Changelog - Release notes and migration guides
- GitHub Releases - Version announcements
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Built with β€οΈ for the Scala 3 community. Special thanks to all contributors who have helped improve this library.
Star β this repo if you find it useful!
Made with Scala 3 | Powered by Inline Macros | Zero Runtime Overhead
Documentation β’ Quickstart β’ GitHub β’ Issues