During the migration of a service from Spring Boot 2.x to Spring Boot 3, I noticed that one of the consequences under the hood is the upgrade from Hibernate 5 to Hibernate 6 (Hibernate is an internal dependency of spring-boot-starter-data-jpa). Suddenly I noticed that after this migration Jpa repository fetch operations for particular entity are failing with the following exception: “java.lang.NullPointerException: Cannot invoke “org.hibernate.collection.spi.PersistentCollection.beginRead()” because “containedCollection” is null”
After the detailed investigation I came to the conclusion that there is a bug in Hibernate 6. For that reason I created a simple demo app (few JPA entity classes, one Spring Data Repository and one integration test which should be run with the docker engine enabled because it uses testcontainers). I created this app in 2 versions which are completely the same in structure and logic and the only difference is that one is based on Spring Boot 2.7.9 and Hibernate 5.6.15 and another one is based on Spring Boot 3.0.4 and Hibernate 6.1.7. Here is the link when you can download these demo apps to try them locally:
Demo apps download link
When you execute the one and only integration test in the first app (hibernate 5.x), the test will pass. But the same test with the same logic will fail in the second version (hibernate 6.x). That is an indicator for a bug in Hibernate 6 in my opinion. And the reason can be noticed in the screenshot which can also be downloaded from the link provided above.
On that screenshot you can notice this piece of Hibernate 6 internal code (CollectionLoaderSubSelectFetch) and you can see that the containedCollection is null on line 77. And that variable is fetched on line 75 where it is extracted from perisenceContext by the key composed of collectionDescriptor and identifier. If you look up to the places that I also highlighted, you will see that in batchFetchQueue we have subselectsByEntityKey and for the entity key WaterTransportVehicle we have 2 resulting entry keys (one for WaterTransportVehicle and LandTransportVehicle). This is the problem because it will mix EntityKey WaterTransportVehicle with the identifier for LandTransportVehicle on line 75 and it will return null. So either something in this logic should be changed or at least null pointer should be handled on line 76.