Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring Data JPA assigned IDs: tell save when an entity is new

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

An assigned primary key is not evidence that a row already exists; make new-state detection explicit before using repository save.

The ambiguous insert

A receipt import assigns its own stable ID before persistence. Spring Data JPA must decide whether save calls persist or merge. Without a nullable version property, a non-null ID can make a new object look existing. A merge may perform an avoidable lookup and returns a managed copy, which matters if callers keep changing the original instance. Entity lifecycle explains why this difference is observable.

Choose one state contract

A nullable @Version field lets the repository regard a null version as new, while also supporting optimistic locking. For an assigned-ID entity without a suitable version, implement Persistable and keep a transient new flag. Flip that flag with @PostPersist and @PostLoad. This is a domain decision: a reconstructed object representing an existing row must not claim it is new. Do not infer existence from an HTTP request field.

Prove both paths

In a repository integration test, save a new assigned-ID receipt and assert one row appears. Load it, change a mutable field, save again, and assert that the same row changes rather than a duplicate insert occurring. Repeat with a detached instance because that route exercises merge. Flush timing can expose SQL but does not prove a committed outcome.

Implementation contract

Java
@Entity
class ReceiptLedger {
    @Id UUID receiptId;
    @Version Long revision;
    String status;
}
// A null revision marks a newly assigned-ID entity for persist().

Cost and verification

Choosing persist avoids a merge lookup for new records. A version column adds storage and conflict checks; correctness is more valuable than hiding one small field.

Common Mistakes

  • Do not equate a non-null, application-assigned ID with an existing database row.
  • Do not keep mutating the original detached object after merge returns a managed copy.
  • Do not use a primitive version field when null must mean new.

Read next

Spring JPA entity lifecycle: managed changes and detached objects, Spring JPA optimistic locking: reject a stale stock update, Spring Data JPA saveAndFlush is a SQL boundary, not a commit, Spring JDBC versioned tenant update: inspect the affected row count, Spring Data JPA repositories: derive a query from mapped properties.

spring
spring-data
jpa
jpa-assigned-id-new-state
Storage details