Repository navigation
v3.0.0
mongo4s 3.0.0
Whole build on Scala 3.9 LTS, typed MongoError instead of driver exceptions, and WireCodec no longer derives
itself unasked. Full migration guide:
COMPATIBILITY.md.
libraryDependencies += "org.mongo4s" %% "mongo4s-core" % "3.0.0"Breaking changes
Every item is a compile error, except the transaction retries.
Scala 3.9 LTSrequired,3.3 LTSdropped.TASTydoes not read forward, and MiMa cannot see that break —
which is what takes the major.bson-calypso,kyoandrapidleave their3.8pin and join the LTS line.WireCodecno longer derives itself for any case class it meets. A field whose type has no codec is a compile
error naming the type; anopaque typekeeps its representation.enumcases and sealed leaves still derive with
their parent.- Driver exceptions arrive as
MongoError. Match onMongoError, or oncause, which holds the original. - Compound
PrimaryKeys are named tuples.compound(o => (userId = o.userId, seq = o.seq), snakeCase)replaces
the positional tuple plus its parallel name list.compound3/compound4removed. Repositoryconditions say what they take:findBy→findByField,updateBy→updateByFilter,
deleteBy→deleteByFilter,getBy→getByFilter.Repository.insertOnereturnsK,insertManyreturnsList[K], notOption[BsonValue]/List[BsonValue].getDirectCollectionno longer takes aFieldNaming— the codec decides how queries spell field names.- The direct path reads numbers like the
BsonValuepath — aLongstored asInt32now decodes; a wrong type
isBsonError.TypeMismatch, not a raw driver exception. aggregate[B]takesBsonDocumentDecoder[B], not a codec. Existing calls compile unchanged.$min/$maxtakeComparableOf, so dates, strings andObjectIds work.$inc/$mulstay numeric.WatchOptionsandRsBridgeConfigarefinal classes withwithXbuilders.RsBridgeConfig.Default→
.default;bufferSizemust be positive.Effect.traverse→Effect.flatTraverse.Effectgainedmonotonicwith a default, so implementors are
unaffected.Streamable.instanceisprivate[mongo4s];mongo4s.bson.DecodeResultmoved tomongo4s-bson-core;
createCollectiongained a defaulted options parameter.withTransactionretries —TransientTransactionErrorrestarts the transaction,UnknownTransactionCommitResult
retries only the commit, both under one 120s deadline. No compile error behind this one;
TransactionOptions.withoutRetriesrestores2.x.
New features
MongoError—DuplicateKey,WriteConflict,ExecutionTimeout,Unauthorized,Unavailable,
BulkWriteFailed,Failed. Translated on thePublisher, so every runtime reports the same type;cause,
code,labelsandhasLabelare all there. Costs nothing measurable on the happy path.explainonfindandaggregate, withExplainVerbosity, plusExplainSummary.of— indexes used,
collection scanned, sorted in memory, execution counters. Reads both shapes a plan can take.selectAs[K]—find(...).selectAs[(name: String, age: Int)]builds projection and decoder from one shape.
A wrong label or type is a compile error. Works on direct collections, where partial reads were not possible.aggregateDirect[B]— pipeline output through aWireCodec[B], AST-free on a direct collection.Repository.findPage(limit, after, filter)— keyset paging over the primary key, lexicographic for compound
keys. No re-scan, and deletions do not shift the rows still to come.TransactionOptions— the retry window plusreadConcern,writeConcern,readPreference,maxCommitTime.- Geospatial filters —
near,nearSphere,within,intersectsover GeoJSON plus legacy2dshapes. An
unclosed polygon ring is refused rather than sent. $bucket,$densify,$setWindowFields, with typed boundaries and window vocabulary.CreateCollectionOptions— capped, validators, time-series, clustered,expireAfter, collation. Combinations
the server accepts and ignores are rejected.- Native
WireCodecforBigDecimal,Instant,UUID,ObjectId— noBsonValueper field. Same BSON on
the wire. Repository.getByField/updateByField/deleteByField, each its…ByFiltercounterpart over
field.equalTo(value).- The fake simulates much more —
aggregate($match,$sort,$skip,$limit,$project,$count,
$group) anddistinct, so aggregating service code is unit-testable without Docker. Sort.byTextScore,Projection.slice/sliceFrom,Index.text,Index.geo2D,
WatchOptions.withExpandedEvents,ChangeEvent.wallTime/splitEvent, andkeyon
Filter/Update/Stage/Accumulator.
Bug fixes
Each reproduced by a failing test first; the fake-versus-server ones are pinned by parity specs against a real
MongoDB, and each spec was run against the unfixed fake to confirm it catches the divergence.
- The fake reported an upsert that inserted as doing nothing — no
_idstamped, sowasUpsertedwasfalse
where the server saystrue. The bulk path reported the entity'sidfield, or the document as JSON. - The fake simulated four of the update operators while the docs said updates were simulated. All are simulated
now,$push's$each/$position/$slice/$sortincluded. - The fake answered several queries differently from the server — array filters compared the whole array,
dotted paths through arrays found nothing,$regexanchored and ignored options,$eq nullmissed absent fields,
$incpromotedInt32toInt64,$counton no input returned a row. PublisherIterator.cancelleaked a thread per cancelled or timed-outrapidstream — no terminal was queued,
so a consumer parked inhasNextstayed parked.RsBridge.unitwrapped failures inCompletionExceptionon some backends, hiding the driver's exception type
and the error labels transaction retries depend on.selectAsreportedMissingFieldfor an absentOptionfield, where reading the whole entity gaveNone.Repository.bulkWritehad noorderedflag and always batched, so an unordered bulk stopped at the first
failing batch.@publicInBinaryon the accessors synthesized forField.ofand theWireCodecderivations — binary-unstable in
a way MiMa cannot check.ReadmeReferencesSpecfails if a README snippet names a member the source does not define.
Benchmarks
Re-measured end to end on stated hardware. Six JMH harnesses, mongo4cats as reference —
BENCHMARKS.md.
WireCodecto real BSON bytes: 2.7–3.2× faster than eithermongo4catscodec, up to 6.9× less allocation.- Against a real MongoDB every single-document operation lands inside run-to-run error for all six stacks.
- Bulk reads pull ahead:
mongo4catsallocates 2.0–13× more onfindAll/findStream. - The server-backed rates are bound by Docker's round trip, not by any library on the page — and the same benchmark
on the same server moved 18% between two runs eight hours apart, so anything narrower is noise.
Compatibility
Scala 3.9 LTS, every module.mongo4s-kyoremains outside the binary compatibility promise while kyo is on a
release candidate.- Building requires
JDK 25— the build stops and says so rather than failing onmongo4s-kyowith
UnsupportedClassVersionError. Build-time only; published artifacts are unaffected. - Tested against JDK 25 and MongoDB 8.2. Note
mongo:8(the8.0line) will not start on Linux kernels 6.19+
(SERVER-121912), which includes Docker Desktop's VM;8.2and
7both run.