Question 19 · Section 16

What is @Version and Why is It Needed

@Version is a JPA annotation for implementing optimistic locking. It automatically tracks entity version and prevents conflicting updates from multiple transactions.

Language versions: English Russian Ukrainian

Overview

@Version is a JPA annotation for implementing optimistic locking. It automatically tracks entity version and prevents conflicting updates from multiple transactions.


Junior Level

What is @Version

@Version - annotation for optimistic locking. Automatically tracks entity version and prevents conflicting updates.

@Entity
public class Order {
    @Id
    private Long id;

    @Version
    private Integer version;  // incremented automatically

    private String status;
}

Why It’s Needed

When two transactions update the same object, @Version guarantees one of them gets an error:

Initial: Order(id=1, version=1, status="new")

Transaction 1: reads (version=1)
Transaction 2: reads (version=1)
Transaction 2: updates -> UPDATE WHERE version=1 -> version=2 OK
Transaction 1: updates -> UPDATE WHERE version=1 -> 0 rows -> OptimisticLockException

How It Works - Simply

1. On INSERT: version = 0. On each UPDATE: version = version + 1.
2. On each UPDATE: version = version + 1
3. WHERE clause: WHERE id = ? AND version = ?
4. If version doesn't match -> 0 rows updated -> OptimisticLockException

Middle Level

Version Field Types

@Version
private Integer version;  // int - increments by 1

@Version
private Long version;     // long - increments by 1

@Version
private Timestamp version;  // timestamp - updated with current time

Detailed Example

@Entity
public class Product {
    @Id
    private Long id;

    @Version
    private Integer version;

    private String name;
    private int stock;
}

// Transaction A
Product pA = em.find(Product.class, 1L);  // version=1, stock=10

// Transaction B
Product pB = em.find(Product.class, 1L);  // version=1, stock=10
pB.setStock(8);
em.flush();  // UPDATE SET stock=8, version=2 WHERE id=1 AND version=1 OK

// Transaction A
pA.setStock(5);
em.flush();  // UPDATE SET stock=5, version=2 WHERE id=1 AND version=1
// -> 0 rows -> OptimisticLockException!
// stock=8 (from B) not overwritten

Common Mistakes

// Manually changing version
order.setVersion(0);  // breaks the locking mechanism

// No @Version for important data
@Entity
public class Account {
    // no @Version -> two threads can simultaneously change balance
}

// Ignoring OptimisticLockException
try {
    em.flush();
} catch (OptimisticLockException e) {
    // silently ignored -> data lost
}

Senior Level

Internal Implementation

Hibernate:
- On INSERT: version = 1 (or 0 for some types)
- On UPDATE: version = version + 1
- WHERE clause: WHERE id = ? AND version = ?
- If 0 rows -> OptimisticLockException

For Timestamp:
- version = CURRENT_TIMESTAMP
- WHERE clause: WHERE id = ? AND version = ?
- Timestamp version is risky: if two transactions update entity in the same millisecond, conflict will NOT be detected. In production use Integer/Long, not Timestamp.

OPTIMISTIC_FORCE_INCREMENT

// Increment version without changing entity
em.lock(order, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

// When needed:
// - On child collection change
// - For forced cache invalidation
// - When entity doesn't change but change needs to be recorded

Audit with @Version

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Order {
    @Id
    private Long id;

    @Version
    private Integer version;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    // version for optimistic locking
    // updatedAt for audit (when was the last change)
}

Performance Implications

@Version overhead:
- +1 column in table
- +1 condition in WHERE on UPDATE
- Practically negligible

Benefits:
- Lost update prevention
- No locks (unlike pessimistic)
- Automatic management

Production Patterns

// Pattern 1: Retry with @Version
@Retryable(value = OptimisticLockException.class, maxAttempts = 3)
@Transactional
public Order updateOrder(OrderDto dto) {
    Order order = entityManager.find(Order.class, dto.id());
    order.setStatus(dto.status());
    return order;
}

// Pattern 2: Refresh and retry
@Transactional
public Order updateWithRefresh(OrderDto dto) {
    Order order = entityManager.find(Order.class, dto.id());
    try {
        order.setStatus(dto.status());
        entityManager.flush();
        return order;
    } catch (OptimisticLockException e) {
        entityManager.refresh(order);  // get actual data
        order.setStatus(dto.status());  // apply to actual
        entityManager.flush();
        return order;
    }
}

Best Practices

@Version on all mutable entities
Retry on OptimisticLockException
Don't change version manually
Monitor conflict frequency
OPTIMISTIC_FORCE_INCREMENT when needed

Without @Version for critical data
Manual version management
Ignoring OptimisticLockException
Pessimistic locking without reason

Interview Cheat Sheet

Must know:

  • @Version - annotation for optimistic locking, automatically increments version on UPDATE
  • WHERE clause: WHERE id = ? AND version = ? - if 0 rows -> OptimisticLockException
  • Types: Integer/Long (increment by 1), Timestamp (updated with current time, risky)
  • On INSERT: version = 0, on first UPDATE: version = 1
  • OPTIMISTIC_FORCE_INCREMENT - increments version without changing entity
  • @Version on all mutable entities - prevents lost updates

Frequent follow-up questions:

  • Why is Timestamp risky for @Version? Two updates in same millisecond - conflict not detected
  • When OPTIMISTIC_FORCE_INCREMENT? Child collection change, cache invalidation, entity unchanged but change needs recording
  • @Version performance overhead? +1 column, +1 condition in WHERE - practically negligible
  • Can version be changed manually? No - breaks locking mechanism

Red flags (DO NOT say):

  • “Timestamp for @Version in production” - conflict in same millisecond not detected
  • “Manual version change” - breaks optimistic locking
  • “Without @Version for Account/Balance” - lost updates possible
  • “I ignore OptimisticLockException” - data silently lost

Related topics:

  • [[17. How to Implement Optimistic Locking in JPA]]
  • [[18. How to Implement Pessimistic Locking in JPA]]
  • [[15. What Does the refresh() Method Do]]
  • [[20. How Do Cascade Operations Work]]