Summary: After upgrading to Spring Boot 3.4.0, there is a Hibernate related error when a transactional method, after saving a row, tries to retrieve some data from a repository method using EntityGraph. This worked in Spring Boot 3.3.5

Details: TestController calls addPayment from TestService1. This method is transactional. This method calls the save() method of a repository Then it calls another service TestService2 method2

This method performs a query using a repository and then another query from another repository which uses EntityGraph.

The exception is

2024-12-09T19:04:46.304+02:00 ERROR 24764 --- [errorCheck] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.EntityEntry.getMaybeLazySet()" because "entityEntry" is null] with root cause

java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.EntityEntry.getMaybeLazySet()" because "entityEntry" is null
    at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstanceSubInitializers(EntityInitializerImpl.java:625) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
    at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:988) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
    at org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.resolveInstance(EntityInitializerImpl.java:97) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]

While trying to reproduce the issue i got another exception, but not constantly

an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: possible non-threadsafe access to the session

If we change the version of Spring Boot to 3.3.5, this works

Steps to reproduce: 1. Run the attached project using postgres as database. errorCheck.zip

  1. Access "http://localhost:8080/test" and an exception will be produced
2024-12-09T19:04:46.304+02:00 ERROR 24764 --- [errorCheck] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.spi.EntityEntry.getMaybeLazySet()" because "entityEntry" is null] with root cause

If we change the Spring Boot version to 3.3.5 the endpoint will return the response "1"

Environment: Spring Boot version: 3.4.0 Java version: 21 Operating System: Windows 11

Attachments Sample project to reproduce the error

Comment From: philwebb

Here's a delomboked version of the sample with a readme for running postgres https://github.com/philwebb/gh-43447

Comment From: philwebb

This looks like a Hibernate issue to me as adding the following to the pom.xml also resolves the issue

<properties>
    <java.version>21</java.version>
    <hibernate.version>6.5.3.Final</hibernate.version>
</properties>

Comment From: dimcookies

Thank you @philwebb for your prompt reply. If this is the case, is this going to be included in one of the upcoming 3.4.X releases?

Comment From: philwebb

Let me investigate a bit more to see if we can identify the cause. I'll update this issue when I have more info.

Comment From: philwebb

I think this is an error in your addPayment method. The original looked like this:

@Transactional
public int addPayment(Long userId) {
    Payment payment = new Payment();
    var invoice = new Invoice();
    invoice.setId(1L);
    payment.setInvoice(invoice);
    AppUser appUser = new AppUser();
    appUser.setId(userId);
    payment.setCreatedFrom(appUser);
    this.paymentRepository.save(payment);
    var test = this.testService2.method2(invoice.getId());
    return test.size();
}

This is manually creating Invoice and AppUser instances outside of Hibernates control.

If you instead fetch the entities as follows, things appear to work:

@Transactional
public int addPayment(Long userId) {
    Payment payment = new Payment();
    var invoice = invoiceRepository.findById(1L).get();
    payment.setInvoice(invoice);
    AppUser appUser = userRepository.findById(userId).get();
    payment.setCreatedFrom(appUser);
    this.paymentRepository.save(payment);
    var test = this.testService2.method2(invoice.getId());
    return test.size();
}

Comment From: dimcookies

@philwebb first of all thank you for your time and effort it took to look into my issue. I understand what you are saying, but this is just a simplified and a single case of a project that it might not be even possible to identify all these cases that will fail if perform the upgrade. I would like to understand why this used to work in the previous version of hibernate and if there is any way to preserve this functionality (other than downgrading hibernate). If you have any suggestions, for example if i should open this issue to hibernate directly, it would be really helpful

Thank you again

Comment From: bclozel

@dimcookies unfortunately, the code that supports this does not live in Spring Boot but in Hibernate. If you would like to provide feedback about your upgrade experience, please do so on the Hibernate issue tracker as there is nothing we can do here. Thanks!