0

I am using Spring Boot 3.5 and have added an @Version field to my entity. This all works fine, but I wonder how I need to handle concurrent updates coming from the REST API.

Suppose 2 users have my web app open. They both see version 1 of my entity.

The first user saves and the version is updated in the database. After a minute or so, the 2nd user saves his changes. I want to return a 409 Conflict at that point.

The saving itself is done in a use case like this:

@Component
@Transactional
class UpdateMyEntity {
 private final MyEntityRepository repo;
 public UpdateMyEntity(MyEntityRepository repo) {
 this.repo = repo;
 }
 public MyEntity execute(MyEntityUpdateParameters parameters) {
 MyEntity item = repo.findById(parameters.id()).orElseThrow();
 item.setName(parameters.name());
 return repo.save(item);
}

At first, I tried to set the version on the MyEntity, but JPA/Hibernate does not take that into account.

Should I manually check the version that the frontend sends me back through the rest api in my use case? Or is there a way to have the database or JPA handle this?

asked Sep 19, 2025 at 14:20
4
  • Did you wrap it in a transaction? Commented Sep 19, 2025 at 15:11
  • Versioning is used to check when serializing your entity data around. In the 'execute' method, you are just taking a name parameter and overwriting the name regardless of any changes made since it was read- so there isn't likely going to be any versioning exceptions except in the case of concurrency in the small window from the 'find' call to the transaction committing. If you need some version control, you'll have to check the version in the entity yourself, or if you don't care if there are other changes, maybe send in the old name and the new one and check the old version of the name Commented Sep 19, 2025 at 15:35
  • 1
    stackoverflow.com/a/78134422/3426309 Commented Sep 20, 2025 at 6:51
  • @K.Nicholas My code had the transaction annotation. Added it now in my example code. Commented Sep 22, 2025 at 5:51

1 Answer 1

2

Versioning control in JPA is based on entities. Merge (or Spring's Save) will take in your unmanaged entity that you have previously read in and modified and check the version in that entity against the version in the database. How it does it is an implementation detail: It can check it upfront against the version value in the cached or read back entity, but it also will include the version value in the UPDATE statement to ensure the row is only modified if the old value it has is still there when the statement is made.

In your case, you are just issuing an immediate 'findById', which will obtain the latest entity data (usually anyway, this still depends on caching options), and then modifying the name parameter. This is equivalent to an 'update X set name=:newName where id=:id' type logic statement. There is only a very small window where it might fail, such as if someone does the same thing concurrently. It will NOT work for the usecase you describe, where two users may read in the same data but issue REST requests to modify parts at slightly offset times.

What you need is to serialize the entity data back, or at least the version of the data, and then force accepting it on the way back.

public MyEntity execute(MyEntityUpdateParameters parameters) {
 MyEntity item = repo.findById(parameters.id()).orElseThrow();
 if (item.getVersion() != parameters.getVersion()
 throw new My409ConflictException();
 item.setName(parameters.name());
 return repo.save(item);
}

You will then also need to have your container handle the JPA persistence exception that may get thrown when there are concurrent updates that it detects when it issues the update statement to ensure you get the 409 message you get, but there are tutorials on handling it

answered Sep 19, 2025 at 15:46
Sign up to request clarification or add additional context in comments.

3 Comments

You still have to wrap the above method in a transaction.
Best practice, absolutely, but not required as save is transactional itself. But if the method is wrapped in its own transaction or participating in one, you don't need the save call as the find method will return a managed entity with changes pushed to the DB when the transaction commits without the explicit save call.
I did a deep dive on this stuff a couple years ago and you may be correct but it seems to me more than just a good idea.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.