Spring Data save() INSERT vs UPDATE Decision
Fast recall
Quick revision
Core answer
Whether you understand the new-vs-existing decision and the persist/merge mechanics behind a single save() call
Assuming save() always inserts, or not knowing merge() runs a SELECT before the UPDATE
Explain Persistable/isNew() for manually assigned IDs, and that dirty checking can update a managed entity without calling save() at all
Interview-ready guide
Interview answer
save delegates the state decision to Spring Data's EntityInformation. With the default strategy, a nullable, non-primitive version is checked first. If no such version exists, a null id means new; a non-null id means not new. New entities go to persist and others to merge. merge returns the managed copy, so callers should use the object returned by save rather than assume the original detached instance became managed. SQL timing and whether the provider selects before updating are provider- and state-dependent; merge is not a promise of exactly two queries.
For example, save(new Customer(null, "Asha")) is normally an insert with a generated id, while saving a detached Customer whose id is 42 is normally merged. Spring Data uses its new-entity detection rules; save is not simply 'id means update' in every mapping.
Spring Data save() INSERT vs UPDATE Decision
Direct Answer
Spring Data JPA calls EntityManager.persist for an entity it considers new and EntityManager.merge otherwise. By default it first checks a non-primitive @Version property, then a nullable identifier: null means new. It does not generally define primitive id value 0 as the rule. Entities with assigned IDs often implement Persistable to supply reliable isNew behavior.
Code Example
@Service
public class ProductService {
private final ProductRepository repository;
ProductService(ProductRepository r) { this.repository = r; }
public Product create(String name, BigDecimal price) {
var p = new Product();
p.setName(name);
p.setPrice(price);
return repository.save(p); // id null -> persist() -> INSERT (id filled)
}
@Transactional
public Product updatePrice(Long id, BigDecimal price) {
var p = repository.findById(id).orElseThrow();
p.setPrice(price);
return p; // managed -> dirty checking -> UPDATE at commit (no save needed)
}
}
Common Mistakes & Pitfalls
- Assuming save() only inserts — a non-null id makes it an UPDATE; reusing a detached entity with an id won't INSERT.
- Calling save() when nothing changed — managed entities update via dirty checking at commit; an extra
save()is redundant. - Manually assigned IDs — a non-null id forces
merge()'s SELECT-before-INSERT; implementPersistableto skip it. - Cross-transaction edits — modifying detached entities means
merge()does a SELECT; prefer fetching fresh in the same transaction.
Check your understanding
Answer these without looking back at the article.
- What is the Persistable interface?
- What happens if you call save() on a manually-assigned ID entity?
- What is the difference between save() and saveAndFlush()?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.