Difference between save and saveAndFlush in Spring data jpa
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
save() persists an entity to the JPA persistence context and defers the SQL INSERT or UPDATE until the transaction commits (or until JPA decides to flush automatically). saveAndFlush() does the same thing but immediately forces a flush, executing the SQL statement right away. In most cases, save() is the correct choice because it allows JPA to batch and optimize database writes. Use saveAndFlush() only when you need the database to reflect the change immediately within the same transaction.
How the Persistence Context Works
To understand the difference, you need to understand JPA's persistence context (also called the first-level cache). When you call save(), JPA does not immediately execute SQL. Instead, it marks the entity as "managed" in the persistence context and queues the change. The actual SQL is sent to the database when one of these happens:
- The transaction commits (most common)
- JPA auto-flushes before a query to ensure consistency
- You explicitly call
flush()or usesaveAndFlush()
save() Behavior
The key insight: save() makes the entity managed, but the timing of the SQL depends on the ID generation strategy and JPA's flush mode.
saveAndFlush() Behavior
After saveAndFlush(), the SQL has been sent to the database. This means database-level constraints (unique, foreign key, check) have been validated. But the transaction is still open. If an exception occurs later, the entire transaction still rolls back.
When saveAndFlush() Is Necessary
Catching Database Constraint Violations Early
Native Queries That Bypass the Persistence Context
Database-Generated Values (Triggers, Defaults)
Comparison Table
| Aspect | save() | saveAndFlush() |
| SQL execution | Deferred until flush/commit | Immediate |
| Returns managed entity | Yes | Yes |
| ID populated | Depends on generation strategy | Always (after INSERT executes) |
| Constraint check | At commit time | At flush time (immediately) |
| Batch optimization | Yes (JPA can batch multiple saves) | No (forces immediate write) |
| Performance | Generally better | Slightly worse per call |
| Transaction still open | Yes | Yes (flush is not commit) |
| Rollback on later failure | Yes | Yes |
| Native query consistency | May see stale data | Database reflects the change |
The flush() Method Directly
saveAndFlush() is a convenience method equivalent to calling save() followed by flush(). You can also flush the entire persistence context manually:
This pattern is important for batch processing. Without periodic flushing and clearing, the persistence context accumulates all entities in memory, which can cause OutOfMemoryError for large batches.
saveAll() vs. Multiple save() Calls
Spring Data JPA also provides saveAll(), which iterates over a collection and calls save() on each entity. It does not batch the SQL by itself. For true batch inserts, configure Hibernate's batch size:
With these settings, saveAll() followed by a single flush will batch the SQL into groups of 50, which is significantly faster than individual inserts.
Common Pitfalls
- Using
saveAndFlush()by default "just to be safe." This defeats JPA's batching optimization and increases the number of database round-trips. Usesave()unless you have a specific reason to flush immediately. - Assuming
saveAndFlush()commits the transaction. It does not. Flush writes SQL to the database but the transaction remains open. A later exception still causes a full rollback. - Calling
save()and then immediately querying with a native query, expecting to see the saved data. Native queries bypass the persistence context. Either use a JPQL query (which triggers auto-flush) or callsaveAndFlush()before the native query. - Forgetting to clear the persistence context during batch processing. Calling
save()thousands of times withoutflush()andclear()keeps all entities in memory. Use periodic flush-and-clear for large batches. - Relying on
saveAndFlush()to catch constraint violations when the exception type depends on the database driver. Wrap the call in a try-catch forDataIntegrityViolationException, not for driver-specific exceptions.
Summary
save()marks an entity as managed and defers SQL execution. It allows JPA to optimize with batching and is the right default choice.saveAndFlush()marks the entity as managed and immediately executes the SQL. Use it when you need constraint validation, native query consistency, or database-generated values within the same transaction.- Neither
save()norsaveAndFlush()commits the transaction. Rollback is still possible after both. - For batch processing, use
save()with periodicflush()andclear()calls, combined with Hibernate'sjdbc.batch_sizesetting. - Default to
save(). Reach forsaveAndFlush()only when deferred execution causes a specific problem.

