MongoDB Update Document
Data rarely stays the same: prices change, stock goes up and down, orders move from "pending" to "shipped". MongoDB updates documents in place with update operators that change only the fields you name, so you never have to read a document, modify it in code and write the whole thing back.
This tutorial covers updateOne(), updateMany(), replaceOne() and findOneAndUpdate(), the field and array operators, upserts, updates with aggregation pipelines, and the Java equivalents.
1. Sample Data
use bookstore
db.books.drop()
db.books.insertMany([
{ _id: 1, title: "Practical Java Streams", category: "Java", price: 499, stock: 40, tags: ["java"] },
{ _id: 2, title: "Designing Event Systems", category: "Kafka", price: 650, stock: 12, tags: ["kafka"] },
{ _id: 3, title: "Document Modeling Basics", category: "Database", price: 420, stock: 0, tags: ["mongodb"] }
])
2. The Update Methods
| Method | What it does |
|---|---|
updateOne(filter, update, options) | Updates the first matching document |
updateMany(filter, update, options) | Updates every matching document |
replaceOne(filter, replacement, options) | Replaces the whole document except _id |
findOneAndUpdate(filter, update, options) | Updates one document and returns it, before or after the change |
3. updateOne() and the Result
db.books.updateOne(
{ _id: 1 }, // filter
{ $set: { price: 459, onSale: true } } // update
)
// {
// acknowledged: true,
// insertedId: null,
// matchedCount: 1,
// modifiedCount: 1,
// upsertedCount: 0
// }
matchedCount: how many documents matched the filter.modifiedCount: how many actually changed. Setting a field to the value it already has counts as matched but not modified.
4. updateMany()
// 10% off every Java book
db.books.updateMany({ category: "Java" }, { $mul: { price: 0.9 } })
// Mark everything out of stock as unavailable
db.books.updateMany({ stock: 0 }, { $set: { available: false } })
{} matches every document in the collection. Before a large updateMany, run countDocuments() with the same filter to see how many documents it will touch.5. Field Update Operators
| Operator | Effect | Example |
|---|---|---|
$set | Sets fields (creates them if missing) | { $set: { "publisher.country": "IN" } } |
$unset | Removes fields | { $unset: { onSale: "" } } |
$inc | Adds to a number (use negative to subtract) | { $inc: { stock: -1 } } |
$mul | Multiplies a number | { $mul: { price: 1.05 } } |
$min / $max | Updates only if the new value is lower / higher | { $min: { lowestPrice: 399 } } |
$rename | Renames a field | { $rename: { stock: "quantity" } } |
$currentDate | Sets a field to the current date | { $currentDate: { updatedAt: true } } |
$setOnInsert | Sets fields only when an upsert inserts | { $setOnInsert: { createdAt: new Date() } } |
// A sale: one copy sold, record the time, remember the lowest price seen
db.books.updateOne(
{ _id: 2, stock: { $gt: 0 } }, // only if in stock
{
$inc: { stock: -1, sold: 1 },
$min: { lowestPrice: 650 },
$currentDate: { updatedAt: true }
}
)
Including stock: { $gt: 0 } in the filter makes the "check and decrement" a single atomic step, so two customers can never both buy the last copy.
6. Array Update Operators
db.books.updateOne({ _id: 1 }, { $push: { tags: "functional" } }) // append
db.books.updateOne({ _id: 1 }, { $addToSet: { tags: "java" } }) // append only if absent
db.books.updateOne({ _id: 1 }, { $push: { tags: { $each: ["lambdas", "jdk21"] } } }) // append several
db.books.updateOne({ _id: 1 }, { $pull: { tags: "jdk21" } }) // remove matching values
db.books.updateOne({ _id: 1 }, { $pop: { tags: 1 } }) // remove last (-1: first)
$push with $sort and $slice keeps a bounded, ordered list, for example the latest five reviews:
db.books.updateOne({ _id: 2 }, {
$push: { recentReviews: {
$each: [ { user: "ken", stars: 5, at: new Date() } ],
$sort: { at: -1 },
$slice: 5
} }
})
7. Updating Elements Inside Arrays
db.orders.insertOne({ _id: 5001, items: [
{ bookId: 1, qty: 1, status: "PACKED" },
{ bookId: 3, qty: 2, status: "BACKORDER" }
] })
// $ : the FIRST element that matched the filter
db.orders.updateOne({ _id: 5001, "items.bookId": 3 }, { $set: { "items.$.qty": 1 } })
// $[] : EVERY element
db.orders.updateOne({ _id: 5001 }, { $set: { "items.$[].status": "SHIPPED" } })
// $[name] with arrayFilters: every element matching a condition
db.orders.updateOne(
{ _id: 5001 },
{ $set: { "items.$[it].gift": true } },
{ arrayFilters: [ { "it.qty": { $gte: 1 } } ] }
)
8. Upserts: Update or Insert
With upsert: true, if no document matches the filter, MongoDB inserts a new one built from the filter's equality fields plus the update:
db.dailyStats.updateOne(
{ day: "2026-10-18", page: "/mongodb-tutorial" },
{ $inc: { views: 1 }, $setOnInsert: { firstSeen: new Date() } },
{ upsert: true }
)
// First call inserts { day, page, views: 1, firstSeen }; later calls just increment views
Put a unique index on the filter fields of upserts that may run concurrently, so two parallel requests cannot create two documents.
9. replaceOne()
replaceOne() swaps the whole document, keeping only _id. Fields not in the replacement disappear, so use it only when you really have the complete new document:
db.books.replaceOne(
{ _id: 3 },
{ title: "Document Modeling Basics, 2nd Edition", category: "Database", price: 480, stock: 30 }
)
10. findOneAndUpdate()
When you need the document back, for example to show the new stock level or to claim a job from a queue, use findOneAndUpdate(). It is atomic, so two workers will never claim the same job:
db.jobs.findOneAndUpdate(
{ status: "QUEUED" },
{ $set: { status: "RUNNING", worker: "worker-7", startedAt: new Date() } },
{ sort: { priority: -1, createdAt: 1 }, returnDocument: "after" }
)
11. Updates with an Aggregation Pipeline
An update can also be an array of aggregation stages ($set, $unset, $replaceWith…), which lets new values depend on other fields of the same document:
// Set a priceBand field computed from the price
db.books.updateMany({}, [
{ $set: { priceBand: {
$switch: {
branches: [
{ case: { $lt: ["$price", 450] }, then: "budget" },
{ case: { $lt: ["$price", 600] }, then: "standard" }
],
default: "premium"
} } } }
])
12. Updating from Java
import com.mongodb.client.*;
import com.mongodb.client.model.*;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Updates.*;
public class UpdateBooksExample {
public static void main(String[] args) {
try (MongoClient client = MongoClients.create("mongodb://localhost:27017")) {
MongoCollection<Document> books = client.getDatabase("bookstore").getCollection("books");
UpdateResult r = books.updateOne(eq("_id", 1),
combine(set("price", 459), addToSet("tags", "functional"), currentDate("updatedAt")));
System.out.println("matched=" + r.getMatchedCount() + " modified=" + r.getModifiedCount());
books.updateMany(eq("stock", 0), set("available", false));
// Atomic stock decrement that returns the new document
Document after = books.findOneAndUpdate(
and(eq("_id", 2), gt("stock", 0)),
inc("stock", -1),
new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER));
// Upsert
client.getDatabase("bookstore").getCollection("dailyStats").updateOne(
and(eq("day", "2026-10-18"), eq("page", "/mongodb-tutorial")),
inc("views", 1),
new UpdateOptions().upsert(true));
}
}
}
13. Summary
- Use
updateOne()orupdateMany()with operators such as$set,$incand$unsetto change only what is needed. - Array operators (
$push,$addToSet,$pull) and positional paths ($,$[],$[name]witharrayFilters) update lists precisely. upsert: trueinserts when nothing matches; protect concurrent upserts with a unique index.replaceOne()replaces the whole document;findOneAndUpdate()updates atomically and returns the document.- Pipeline updates compute new values from existing fields.