What is the difference between updateMulti, updateFirst, insert, and save on MongoTemplate?
answer
- updateFirst = 1 match, updateMulti = all matches
- Update = $set/$inc/$push server-side
- save = upsert by _id, full replace
- insert = always new, dup _id throws
- save clobbers missing fields; updates merge
basics
~20 supdateFirst modifies the first matching document, updateMulti modifies all matching ones — both take a Query and an Update with operators like $set. insert always creates a new document; save inserts if new or replaces the whole document if the _id already exists (upsert-by-id).
solid answer
~40 sMongoTemplate offers targeted, server-side updates versus whole-document writes. updateFirst(query, update, Class) applies an Update to the FIRST document matching the query; updateMulti(query, update, Class) applies it to ALL matches — the update is server-side using operators (Update.update("status","X").inc("count",1).set(...).push(...)), so you never load the documents. upsert(query, update, Class) inserts a new doc if nothing matched. In contrast, insert(entity) always issues an insert and fails on duplicate _id; save(entity) does an upsert keyed by _id — insert if _id is absent/new, otherwise a full replacement of the existing document. The key distinction: updateMulti/updateFirst do partial field-level modification server-side and return an UpdateResult with matched/modified counts, while save serializes the whole Java object and replaces the stored document, which can clobber fields not present on your object.
code
java · 19 linesimport org.springframework.data.mongodb.core.query.Update;
import com.mongodb.client.result.UpdateResult;
import static org.springframework.data.mongodb.core.query.Criteria.where;
// Bulk, server-side partial update of ALL matches
UpdateResult res = mongoTemplate.updateMulti(
new Query(where("status").is("NEW").and("createdAt").lt(cutoff)),
new Update().set("status", "ARCHIVED").inc("revision", 1),
Order.class);
long changed = res.getModifiedCount();
// Upsert: update if present, else insert
mongoTemplate.upsert(
new Query(where("userId").is(uid)),
new Update().set("lastSeen", Instant.now()),
Session.class);
// Whole-document insert-or-replace by _id
mongoTemplate.save(order); // replaces entire stored docgo deeper
Knows updateFirst vs updateMulti scope and that save inserts-or-replaces.
Explains server-side Update operators, upsert semantics, and the save-replaces-whole-doc gotcha.
Adds UpdateResult counters, concurrency safety of $set vs read-modify-save, and @Version/optimistic locking behavior.
Reasons about lifecycle-callback bypass in updates, bulk-write efficiency, and choosing merge-vs-replace semantics for data-integrity under concurrency.
These four methods split along two axes: **partial vs whole-document** writes, and **single vs multi** document scope. **Update object.** `org.springframework.data.mongodb.core.query.Update` is a builder mirroring MongoDB update operators: - `.set(field, val)` → `$set` (assign a field) - `.unset(field)` → `$unset` (remove a field) - `.inc(field, n)` → `$inc` (atomic increment) - `.push(field, val)` / `.addToSet(...)` / `.pull(...)` → array operators - `.currentDate(field)`, `.rename(...)`, `.max/.min`, etc. These run **on the server**; the documents are never fetched into the JVM, which is efficient and avoids read-modify-write races. **updateFirst(query, update, Class).** Applies the Update to the **first** matching document only. Returns an `UpdateResult` exposing `getMatchedCount()` and `getModifiedCount()`. Useful when a query is expected to hit one doc, or you deliberately want one. **updateMulti(query, update, Class).** Same, but applies to **every** matching document — the batch/bulk field update. This is the go-to for "set status = ARCHIVED on all orders older than X". **upsert(query, update, Class).** Like updateFirst but if nothing matches, MongoDB **inserts** a new document built from the query's equality conditions plus the update. `UpdateResult.getUpsertedId()` returns the new _id. **insert(entity).** Always performs an insert. If a document with the same `_id` already exists, it throws `DuplicateKeyException`. Use when you know the object is new. `insert(Collection)` does a batch insert. **save(entity).** Performs an **upsert keyed by `_id`**: if the object's `_id` is null/new, it inserts; if the `_id` matches an existing document, it **fully replaces** that document. Crucial gotcha: `save` serializes your whole Java object, so any field not populated on the object is **lost** in the stored doc — save does a replacement, not a merge. If you loaded a partial projection and then save it, you can wipe fields. **Key contrasts / gotchas.** - `updateMulti` with `$set` merges specific fields, preserving the rest; `save` replaces the entire document. Prefer targeted updates when you only touch a couple fields, especially under concurrency. - `updateFirst` matched-but-not-modified (e.g. setting a field to its current value) yields modifiedCount 0 even though matchedCount is 1 — check the right counter. - `insert` vs `save`: insert never replaces; save can. Batch insert bypasses some per-entity save logic. - Optimistic locking: with a `@Version` field, `save` throws `OptimisticLockingFailureException` on stale versions; raw `updateMulti` does not participate in versioning unless you add the version predicate yourself. - Lifecycle: `save`/`insert` fire Spring Data lifecycle events and run converters; `updateFirst/updateMulti` operate at the document level and do **not** run entity converters/lifecycle callbacks on the modified docs. **When to use.** Use updateFirst/updateMulti/upsert for field-level, server-side, concurrency-safe changes and bulk operations. Use insert for guaranteed-new documents, and save when you own the full object and want insert-or-replace semantics.
- You loaded a document with a field projection (only 2 fields), then called save() on it. What happens?save replaces the whole document, so fields you did not include in the projection are absent on the object and get wiped from storage. Use updateFirst with $set to touch only the intended fields.
- How do matchedCount and modifiedCount differ in the UpdateResult?matchedCount is how many docs matched the query; modifiedCount is how many actually changed. Setting a field to its existing value matches but does not modify, so modifiedCount can be 0 while matchedCount is 1.
saying these in an interview costs you the question
- Saying save() merges fields like $set (it fully replaces the document)
- Thinking updateMulti loads documents into memory first
- Claiming insert() will replace an existing doc with the same _id
- Believing updateMulti runs entity lifecycle callbacks/converters