# Trying to understand StaleObjectStateException after upgrade to 6.6

**URL:** <https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553>\
**Category:** Hibernate ORM\
**Created:** [June 10, 2025, 7:31am UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553 "2025-06-10T07:31:34Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![martin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/martin/32/4040_2.png) [@martin](https://discourse.hibernate.org/u/martin)\
**Post date:** [June 10, 2025, 7:31am UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/1 "2025-06-10T07:31:34Z")

</div>

Hey everyone,

I am running an application that has been using Hibernate since 2005 or so. A few weeks ago I decided to get the dreaded criteria API migration behind me and went straight from 5.6 to 6.6. Last week I was ready to start initial load tests with real’ish data and immediately saw the logs littered with StaleObjectStateExceptions. I tried a whole lot of things to resolve the issue. And I got the number of exceptions down quite a bit. But since the code/logic as such hasn’t changed - short of migrating some queries to the new criteria API - I am trying to understand where this problem is coming from.

Here’s a rough outline of what my code does, logically:

- The code does scheduling for aircraft.
- For a given aircraft, belonging to a given company, it loads all existing flights as well as the desired flight plan.
- It generates all the flights missing in the near future.
- It then goes over all existing and new flights and makes sure they can be operated
- It adds delays, cancels flights or adds transfers to make the overall flight plan work.

Interestingly, the StaleObjectStateExceptions never relate to the aircraft or their flights. It’s always _an airline_ that shows up in the error. And in the majority of cases, it isn’t the direct operator of the aircraft, but a _related company_. Some context, with an emphasis on things that _I think_ might be related to the issue:

- Each aircraft has an `operator` (ManyToOne) of type `Enterprise`
- Each `Enterprise` has an attribute `parentCompany` (ManyToOne) of type `Company`
- That `parentCompany` always is an `Enterprise`
- `Enterprise` is a sub-class of `Company`
- `Company` is a sub-class of `Addressee`
- The hierarchy is mapped as a `@Inheritance(strategy = InheritanceType.JOINED)`
- Optimistic locking is enabled through a `@Version private Long version;` on `Addressee`

Now, the `operator` of an aircraft is actually modified during flight planning, but not always. But the parent companies almost certainly aren’t (and we never had this this issue in near 20 years of running this code). And yet, it now seems that Hibernate is overly cautious and assumes they have changed, causing these exceptions left and right.

The most effective change I have tried to far was setting the `parentCompany` attribute to be lazy-loaded. This reduced the amount of exceptions quite a bit, but it didn’t alleviate the problem completely.

So I would like to understand what might be causing this and what can be done to decrease the chances of this happening. Is my versioning set up correctly? Does my inheritance mapping require a review? Is there a way to to “help” Hibernate to make the right call?

I would be very thankful for your insights.

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [June 18, 2025, 12:01pm UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/2 "2025-06-18T12:01:36Z")

</div>

Did you go through the migration guides yet to see if one of the behavior changes might affect you? It sounds like you might be hit by something like [this](https://github.com/hibernate/hibernate-orm/blob/6.6/migration-guide.adoc#merge-versioned-deleted). Maybe an `Airline` object was created through `new` and attached to an object graph that then somehow triggers merge cascading. So say if you merge a `Airline` with the wrong optimistic version, obviously it will fail.

It would be helpful if you could create a reduced example to reproduce this issue for further debugging.

---

<div class="post-metadata">

**Author:** ![martin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/martin/32/4040_2.png) [@martin](https://discourse.hibernate.org/u/martin)\
**Post date:** [June 23, 2025, 9:55am UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/3 "2025-06-23T09:55:17Z")

</div>

Thanks a lot for your reply!

> [@beikov](#):
>
> It sounds like you might be hit by something like [this](https://github.com/hibernate/hibernate-orm/blob/6.6/migration-guide.adoc#merge-versioned-deleted).

I suspected as much, but I doubt that’s the reason. No airlines are created in this process. All companies either directly (as operators) or indirectly (as parent) involved already exist before the transaction. New flight objects are created which reference their operators, of course.

The overall system is highly concurrent. So it is likely (if not certain) that _parent companies_ are in fact modified in parallel transactions. But the code is written in such a way that all transactions are serialised _per operator_, meaning a change to Airline A could happen at the same time as a change to Airline B, but no two changes to Airline A could happen in parallel. My suspicion is that if Airline A is a parent of Airline B and transactions happen for both at the same time, the transaction for Airline B falsely assumes that there’s a competing write to Airline A even if the object didn’t actually change _in the Airline B transaction_.

Is there any transitive mechanism like that that I need to be aware of? Where is the “right” location for the version field in a case like this? Should it be on `Addressee` (the shared root), the `Company` or the `Enterprise`?

> [@beikov](#):
>
> It would be helpful if you could create a reduced example to reproduce this issue for further debugging.

Due to the verbosity of the actual code involved, I’m afraid this isn’t feasible. But if all else fails, I could try to reproduce this in vitro. But I have a gut-feeling about the chances of this succeeding 😅

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [June 23, 2025, 1:49pm UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/4 "2025-06-23T13:49:54Z")

</div>

It’s hard to say what goes on exactly. Can you post the full stack traces of these `StaleObjectStateException`? That might help understand where this comes from.

---

<div class="post-metadata">

**Author:** ![martin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/martin/32/4040_2.png) [@martin](https://discourse.hibernate.org/u/martin)\
**Post date:** [June 24, 2025, 12:50pm UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/5 "2025-06-24T12:50:42Z")

</div>

This is one I grabbed straight off the logs:

```auto
2025-06-24T12:43:37.040Z ERROR 1 --- [s-dispatcher-43] d.a.c.EntityActor : StaleObjectStateException during operation AircraftOperationsOperation. Entity ID: 234, stale object ID: 231
 
 org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [x.x.x.Enterprise#231]
 	at org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identifiedResultsCheck(ModelMutationHelper.java:75) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.lambda$doStaticUpdate$9(UpdateCoordinatorStandard.java:785) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.checkResults(ModelMutationHelper.java:50) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.performNonBatchedMutation(AbstractMutationExecutor.java:141) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorStandard.lambda$performNonBatchedOperations$2(MutationExecutorStandard.java:258) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at java.base/java.util.TreeMap.forEach(Unknown Source) ~[?:?]
 	at org.hibernate.engine.jdbc.mutation.internal.PreparedStatementGroupStandard.forEachStatement(PreparedStatementGroupStandard.java:91) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorStandard.performNonBatchedOperations(MutationExecutorStandard.java:258) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.doStaticUpdate(UpdateCoordinatorStandard.java:781) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.performUpdate(UpdateCoordinatorStandard.java:328) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.update(UpdateCoordinatorStandard.java:245) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:169) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:644) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:511) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:414) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:41) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1429) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:491) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2354) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:1978) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:439) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:169) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:267) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101) ~[hibernate-core-6.6.15.Final.jar:6.6.15.Final]
 	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562) ~[spring-orm-6.2.7.jar:6.2.7]
 	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:795) ~[spring-tx-6.2.7.jar:6.2.7]
 	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:758) ~[spring-tx-6.2.7.jar:6.2.7]
 	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:152) ~[spring-tx-6.2.7.jar:6.2.7]

```

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [June 24, 2025, 5:07pm UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/6 "2025-06-24T17:07:44Z")

</div>

If you can debug the application while reproducing, I would suggest you put a breakpoint into the constructor of `EntityUpdateAction` with a condition to only observe events for `Enterprise` entities and inspect the thread stack when this unexpected thing happens. Maybe you can backtrack to find out why the entity is considered dirty.

---

<div class="post-metadata">

**Author:** ![martin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/martin/32/4040_2.png) [@martin](https://discourse.hibernate.org/u/martin)\
**Post date:** [June 25, 2025, 8:27am UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/7 "2025-06-25T08:27:14Z")

</div>

Thanks a lot, I’ll try to look into it.

---

<div class="post-metadata">

**Author:** ![martin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/martin/32/4040_2.png) [@martin](https://discourse.hibernate.org/u/martin)\
**Post date:** [June 26, 2025, 9:16am UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/8 "2025-06-26T09:16:48Z")

</div>

So I followed your recommendation. It’s really hard to isolate the exact situations I am looking for (because updates to `Enterprise` objects naturally happen all the time). But stopping on `StaleObjectStateException` being thrown and then having a look at the `EntityUpdateAction` frame produced the following lead: All such errors have in common that one of the dirty fields is an object map attribute of the `Company` class. Sometimes it’s the only dirty field.

The field is mapped as such:

```auto
@Column(name = "o_custom_settings_data")
@JdbcTypeCode(Types.VARBINARY)
@Convert(converter = ObjectMapConverter.class)
private Map<String, Object> customSettingsData;

```

This map is never modified automatically. Only by direct user interaction. So I figure it has something to do with how the “dirty-check” on that field is done. Maybe my mapping isn’t correct to begin with? Is a converter the right way to go here?

All assuming that this field is actually the culprit, of course.

---

<div class="post-metadata">

**Author:** ![beikov](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/beikov/32/258_2.png) [@beikov](https://discourse.hibernate.org/u/beikov)\
**Post date:** [June 26, 2025, 11:05am UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/9 "2025-06-26T11:05:53Z")

</div>

There is nothing inherently wrong with your mapping, but surely there must be something wrong in your application if the map is mutated, even though you weren’t expecting any changes.

---

<div class="post-metadata">

**Author:** ![martin](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/martin/32/4040_2.png) [@martin](https://discourse.hibernate.org/u/martin)\
**Post date:** [June 27, 2025, 4:53pm UTC](https://discourse.hibernate.org/t/trying-to-understand-staleobjectstateexception-after-upgrade-to-6-6/11553/10 "2025-06-27T16:53:34Z")

</div>

Solved it. There actually weren’t any active modifications of the maps. And it was in fact the dirty-check that caused the issue. But the bug lived 100% on my end of things, of course 😃

I feel a bit stupid now, but the map sometimes contained entries of type `Object[]` holding two serializable values. If two different instances of said array existed in the context, `equals()` failed despite the values being logically the same. Changed the serialisation format of those values and the exceptions disappeared. Future-me should probably revisit this whole map-based serialisation scheme in general…

Either way, thank you very much for your assistance!
