Flush can surface a database constraint error before method return while the surrounding transaction may still roll back.
Spring Data JPA saveAndFlush is a SQL boundary, not a commit
Distinguish three events
save places or merges entity state in the persistence context. Flush synchronizes pending changes with the database inside the current transaction. Commit makes the transaction durable and visible according to database isolation. Calling saveAndFlush can force a unique-key rejection earlier, but a later failure still rolls back the flushed row. Transaction ownership remains at the service boundary, not the repository call.
Do not publish on flush
An order acceptance service writes a receipt and an outbox row in one transaction. If it sends a broker message immediately after saveAndFlush, a later rollback can leave a message announcing a receipt that never committed. Persist an outbox obligation in the same database transaction and deliver it later through the relay boundary. A flush also does not make a concurrent transaction see an uncommitted row.
Exercise the rollback path
Save a receipt with a unique tenant and external key, flush it, then throw a test exception before method exit. Assert the row is absent in a new transaction. Repeat with a duplicate key to check when the exception appears for the chosen database and constraint mode. Some constraints are deferred until commit, so do not promise that every violation is caught by flush.
Implementation contract
@Transactional
public UUID accept(ReceiptCommand command) {
ReceiptEntity receipt = receipts.saveAndFlush(
ReceiptEntity.from(command));
outbox.save(OutboxEvent.receiptAccepted(receipt.getId()));
return receipt.getId();
}Cost and verification
An explicit flush adds a synchronization point and can reduce batching opportunities. Use it when an earlier database check or generated value is required, not after every save. The transaction still holds resources until commit or rollback.
Common Mistakes
- Do not treat saveAndFlush as a durable commit.
- Do not send an external message after flush and before commit.
- Do not assume every database constraint is checked before commit.
Read next
Spring TransactionTemplate: roll back a failed multi-row change, Spring outbox transaction boundaries: where atomicity ends, Spring Data JPA bulk update: the managed entity can still hold the old value, Spring read-only transaction: performance hint, not a write guard, Spring JPA optimistic locking: reject a stale stock update.
Test this boundary
Spring Data JPA test: flush and clear before trusting a read
