Repository navigation
v2.0.0
mongo4s 2.0.0
Two things drive it. The write API's options moved out of method parameters and into immutable values, which is what
lets every future option ship in a 2.x minor instead of another major. And an independent audit of the codebase
found a set of real defects, all fixed here, each reproduced by a failing test first.
The compiler catches every breaking change — none of them alters behaviour silently. The full migration guide is in
COMPATIBILITY.md.
libraryDependencies += "org.mongo4s" %% "mongo4s-core" % "2.0.0"New features
Read and write concerns per database and collection. withReadConcern, withWriteConcern and
withReadPreference return a new handle rather than mutating the one you hold, so per-operation control is just
chaining: collection.withWriteConcern(WriteConcern.MAJORITY).insertOne(x). Previously these could only be set for
the whole client, and there was no way to build a MongoCollection from a driver collection you had narrowed
yourself.
mongo4s-testkit, a new published module. FakeMongoCollection used to live in mongo4s-repositories' test
sources, which meant nobody outside this build could use it. It is now published, together with a new
FakeRepository — a real BaseMongoRepository over a fake collection, so the repository logic under test is the one
that ships.
arrayFilters and the positional operators. $[] and $[identifier] are ordinary path segments, so
Field.of[Order, List[Item]](_.items) / "$[low]" / "qty" builds them; the filters themselves go in UpdateOptions.
Write, delete and count options. collation, hint and comment on every write, bypassDocumentValidation
where a whole document is written, and limit/skip/maxTime on count.
$each modifiers on pushAll — $slice, $sort, $position, for a capped, ordered array maintained by the
server rather than by the application.
More index types — hashed, 2dsphere, 2d, plus hidden, collation and wildcardProjection. Wildcard
indexes needed no new API: $** is a stored path.
Richer aggregation — $out and $merge take a target database, and $merge its on/whenMatched/
whenNotMatched policies; $lookup gained its sub-pipeline form with let; $graphLookup is new.
omitNoneFields (default on). A None field is left out of the document instead of written as an explicit
null, which is what a consumer asked for after reporting a 2×+ drop in memory footprint. Reads are unaffected in
both directions, so mixed collections keep working.
BsonTypeName is public and an enum, so hasType takes a value rather than a $type alias string that a typo
turns into a filter matching nothing.
API changes
Every item here is a compile error, not a behaviour change.
Write options are values, not parameters. UpdateOptions, ReplaceOptions, DeleteOptions, CountOptions and
the entity-typed FindOneAndUpdateOptions[A] / FindOneAndReplaceOptions[A] / FindOneAndDeleteOptions[A]:
collection.updateOne(filter, update, upsert = true) // 1.x
collection.updateOne(filter, update, UpdateOptions.upsert) // 2.0deleteOne, deleteMany and count gained an options parameter. WriteCommand and Repository follow.
Projection is two types. Mixing inclusion with exclusion no longer compiles, where it used to silently discard
whatever you listed first and return more fields than you asked for. Projection.empty returns
Projection.Everything[E].
Index and WireCodecConfig are no longer case classes. Both are final classes with private constructors and
withX builders, for the same reason: a case class cannot gain a field without breaking apply/copy again in
every release, and options keep arriving.
hasType takes BsonTypeName, and BsonError.TypeMismatch carries it in both fields. The rendered message is
unchanged.
MongoCollection and MongoDatabase gained three abstract methods — the concern setters — which matters if you
implement either trait outside this library. Stage gained LookupPipeline and GraphLookup cases, which matters
if you match on it exhaustively.
FakeMongoCollection moved to mongo4s.testkit, in the new module.
Bug fixes
Each of these was reproduced by a failing test before being fixed; the codec and query fixes are pinned against a
real MongoDB.
- A sealed hierarchy stored as the root entity of a direct collection could be written but never read back. The
decoder required the_typediscriminator to be the document's first field, and the server stores_idfirst.
EitherandIorcarried their own copies of the same logic and the same defect. - Every write threw under an unacknowledged write concern. With
w=0the driver has no result to report, and
each getter raisedUnsupportedOperationException. Results now come back empty. Decimal128lost precision above 2^53 by being read throughDouble; theLongrange check admitted exactly
2^63 and saturated; a non-finite value threwArithmeticExceptionpast theEither.- An explicit
BsonEncoder/BsonDecoderpair and automaticWireCodecderivation were ambiguous, so a case
class carrying both resolved no codec at all. Derivation is now the lower priority. cats.data.Iordecoded its two sides by position, silently swapping them when the stored field order differed
and both sides shared a wire type.SortandIndexkept a repeated field's original position, soasc(a).asc(b).desc(a)sorted byafirst.insertManyreturned ids in the driver's map iteration order rather than by command position.aggregate(...).firstappended$limitafter$out/$merge, which the server rejects.- A projection that mixed inclusion and exclusion silently returned extra fields, and
Projection.excludeId
followed byincludesilently lost the_idexclusion. WatchOptions' three starting points were not mutually exclusive, so a resume token and an operation time
could both reach the server, which rejects that.- A bare discriminator naming a case that carries fields raised
IndexOutOfBoundsExceptioninstead of a
BsonError, and a subtype declaring its own_typefield wrote duplicate BSON keys. - rapid's
MongoClientResourceleaked the client whenusethrew while its task was being built. - zio's
bracketCaseswallowed a finalizer error on success, where every other runtime raises it. - kyo's
streamfailed as aClassCastExceptionwhen handed aStreamableit had not derived itself. - Reads on the direct path are strict about BSON numeric types — an
Int32where the model saysLongis an
error, unlike the lenientgetCollectionpath. This was always true; it is now pinned by tests and documented.
Known gaps
GridFS, geospatial operators, CreateCollectionOptions, transaction retry semantics and a circe bridge are not
covered. Each has a workaround today, and all but one are additive — see
ROADMAP.md.
Compatibility
Scala 3.3 LTS, except mongo4s-bson-calypso, mongo4s-kyo and mongo4s-rapid, which are pinned to 3.8 by their
upstream dependencies and cannot be consumed from a 3.3 project. mongo4s-kyo sits outside the binary compatibility
promise while kyo is on a release candidate. Tested against JDK 25 and MongoDB 7.