Skip to main content

Find

The find operation retrieves documents from a collection. It supports filtering, sorting, pagination, field projection, and streaming for large result sets.

Basic usage

import cats.effect.IO
import mongo4cats.bson.Document
import mongo4cats.collection.MongoCollection

val collection: MongoCollection[IO, Document] = ???

// Stream all documents
val stream: fs2.Stream[IO, Document] = collection.find.stream

// Collect all documents into memory
val all: IO[Iterable[Document]] = collection.find.all

// Fetch only the first matching document
val first: IO[Option[Document]] = collection.find.first

Filtering

Pass a Filter to restrict which documents are returned. Filters are composable with && (AND), || (OR), and .not:

import mongo4cats.operations.Filter

// Equality
val byName = Filter.eq("name", "Alice")
val byStatus = Filter.eq("status", "active")

// Comparisons
val adults = Filter.gte("age", 18)
val recent = Filter.gt("createdAt", Instant.parse("2024-01-01T00:00:00Z"))

// Combine with &&
val result: IO[Iterable[Document]] = collection.find(byName && byStatus).all

// Combine with ||
val eitherResult: IO[Option[Document]] = collection.find(byName || Filter.eq("name", "Bob")).first

// Negate
val notActive: IO[Iterable[Document]] = collection.find(byStatus.not).all

Filter reference

FilterDescription
Filter.emptyMatches all documents
Filter.eq(field, value)Field equals value
Filter.ne(field, value)Field not equal to value
Filter.gt(field, value)Field greater than value
Filter.gte(field, value)Field greater than or equal to value
Filter.lt(field, value)Field less than value
Filter.lte(field, value)Field less than or equal to value
Filter.in(field, values)Field value is in the list
Filter.nin(field, values)Field value is not in the list
Filter.exists(field)Field is present in the document
Filter.notExists(field)Field is absent from the document
Filter.isNull(field)Field value is null
Filter.regex(field, pattern)Field matches a regex pattern (String or scala.util.matching.Regex)
Filter.text(search)Full-text search (requires a text index)
Filter.all(field, values)Array field contains all the given values
Filter.elemMatch(field, filter)At least one array element matches the filter
Filter.size(field, n)Array field has exactly n elements
Filter.typeIs(field, bsonType)Field is of the given BSON type
Filter.mod(field, divisor, remainder)Modulo operation
Filter.idEq(value)Match on _id
Filter.and(filters*)Logical AND
Filter.or(filters*)Logical OR
Filter.where(jsExpr)Match using a JavaScript expression
Filter.jsonSchema(schema)Match using a JSON schema

Geospatial filters (geoWithin, geoIntersects, near, nearSphere, etc.) are also available.

Sorting

import mongo4cats.operations.Sort

// Ascending by one field
collection.find.sort(Sort.asc("name")).all

// Descending by one field
collection.find.sort(Sort.desc("createdAt")).all

// Multi-field: ascending by "category", then descending by "score"
collection.find.sort(Sort.asc("category").desc("score")).all

// Shorthand methods on the query builder
collection.find.sortBy("name").sortByDesc("createdAt").all

// Text score sort (requires a text index and text filter)
collection.find(Filter.text("search term"))
.sort(Sort.metaTextScore("score"))
.all

Pagination

// Skip the first 20 results and return the next 10
collection.find.skip(20).limit(10).all

Projection

Use Projection to include or exclude specific fields, reducing the amount of data transferred:

import mongo4cats.operations.Projection

// Include only name and email (and implicitly _id)
collection.find.projection(Projection.include("name").include("email")).all

// Exclude _id, include everything else
collection.find.projection(Projection.excludeId).all

// Exclude specific fields
collection.find.projection(Projection.exclude("sensitiveField")).all

// Return a slice of an array field (skip 0, take 5)
collection.find.projection(Projection.slice("tags", 5)).all

Combining options

Options can be chained in any order:

collection.find(Filter.eq("status", "active"))
.sort(Sort.desc("score"))
.skip(10)
.limit(20)
.projection(Projection.excludeId)
.all

Streaming large result sets

Use .stream instead of .all to process documents one at a time without loading the entire result set into memory:

import fs2.Stream

val stream: fs2.Stream[IO, Document] =
collection.find(Filter.gte("score", 0)).sort(Sort.desc("score")).stream

stream.evalMap(doc => IO.println(doc)).compile.drain

Atomic find-and-modify operations

MongoCollection[F, T] also supports atomic operations that find a document and modify it in one step:

import mongo4cats.operations.{Filter, Update}
import mongo4cats.models.collection.FindOneAndUpdateOptions

// Find the first matching document and update it, returning the document AFTER the update
val updated: IO[Option[Document]] = collection.findOneAndUpdate(
Filter.eq("name", "Alice"),
Update.inc("loginCount", 1),
FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
)

// Find and delete, returning the deleted document
val deleted: IO[Option[Document]] = collection.findOneAndDelete(Filter.eq("name", "Bob"))

// Find and replace
val replaced: IO[Option[Document]] = collection.findOneAndReplace(
Filter.eq("_id", someId),
newDocument
)

Using with a client session (transactions)

All find operations accept an optional ClientSession[F] as the first argument to run within a transaction:

client.startSession.use { session =>
collection.find(session, Filter.eq("status", "pending")).all
}