Issue Summary
In a Spring Boot 3.3.5 application using GraalVM (22.3.0) for native image compilation, the following exception occurs when attempting to initialize lazy-loaded Hibernate entities:
org.springframework.security.authentication.InternalAuthenticationServiceException:
Unable to perform requested lazy initialization
[io.github.susimsek.springnextjssamples.entity.RoleEntity.name] - session is closed and settings disallow loading outside the Session
This issue occurs only in the GraalVM Native Image environment. The error does not appear when running the application in a standard JVM environment.
Project Repository
- Repository: https://github.com/susimsek/spring-graalvm-demo
Environment Details
- Java Version: 21
- Spring Boot Version: 3.3.5
- GraalVM Version: 22.3.0
- Hibernate Version: 6.5.3.Final
Steps to Reproduce
- Clone the repository spring-graalvm-demo.
- Configure the project for native image generation with GraalVM.
- Run the native image build with the following command:
bash
./mvnw native:compile -Pnative
- When accessing endpoints that require lazy initialization of Hibernate entities, observe the
LazyInitializationException
.
Analysis
The exception suggests that the Hibernate session is closed before the lazy-loaded entity (RoleEntity.name
) can be accessed, causing a LazyInitializationException
. This may indicate that GraalVM's native image environment is not handling Hibernate's lazy loading proxy as expected, particularly under Spring Security's context.
Related Configuration
The application uses hibernate-enhance-maven-plugin
to enable lazy loading and other entity enhancements:
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version>
<executions>
<execution>
<id>enhance</id>
<configuration>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
Expected Behavior
The application should be able to lazily load entity fields in the GraalVM native environment as it does in a standard JVM environment without throwing a LazyInitializationException
.
Additional Notes
- The issue appears to be specific to GraalVM's native image behavior with Hibernate and Spring Security, particularly with lazy initialization outside an active session.
Comment From: snicoll
@susimsek sorry but that's not really a sample you're sharing. Besides you're configuring a lot of things manually and therefore might miss some of the optimizations we do for native builds.
If you want support please take the time to share a minimal sample that reproduces the problem. Creating a project from start.spring.io is our recommendation.
Comment From: susimsek
Hi, I’ve created a minimal reproducible sample following your advice, using start.spring.io as the base. You can find it here:
https://github.com/susimsek/spring-graalvm-demo
I’m encountering a 401 Unauthorized response when making a POST request to /api/v1/auth/token with valid credentials after running the application in native mode. Below is the curl command I am using:
curl -X POST http://localhost:8080/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "password"
}'
It seems this issue is related to lazy initialization. Specifically, setting spring.jpa.open-in-view=false
causes the 401 Unauthorized error in the native application, suggesting a potential configuration or proxy-related issue in native mode.
Comment From: wilkinsona
Thanks for the sample.
When running on the JVM, the call to DomainUserDetailsService.loadUserByUsername
causes a transaction to be started and the session is then associated with that transaction:
2024-11-06T09:04:13.628Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Securing POST /api/v1/auth/token
2024-11-06T09:04:13.632Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2024-11-06T09:04:13.633Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Secured POST /api/v1/auth/token
2024-11-06T09:04:13.635Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : POST "/api/v1/auth/token", parameters={}
2024-11-06T09:04:13.636Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.github.susimsek.springgraalvmdemo.controller.AuthController#login(LoginRequestDTO)
2024-11-06T09:04:13.689Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [LoginRequestDTO[username=admin, password=password]]
2024-11-06T09:04:13.771Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [io.github.susimsek.springgraalvmdemo.service.DomainUserDetailsService.loadUserByUsername]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2024-11-06T09:04:13.771Z DEBUG 85010 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1934590377<open>)] for JPA transaction
Contrastingly, when run in a native image, no transaction is started:
2024-11-06T09:05:07.825Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Securing POST /api/v1/auth/token
2024-11-06T09:05:07.826Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2024-11-06T09:05:07.826Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Secured POST /api/v1/auth/token
2024-11-06T09:05:07.826Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : POST "/api/v1/auth/token", parameters={}
2024-11-06T09:05:07.826Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.github.susimsek.springgraalvmdemo.controller.AuthController#login(LoginRequestDTO)
2024-11-06T09:05:07.830Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [LoginRequestDTO[username=admin, password=password]]
2024-11-06T09:05:07.907Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
2024-11-06T09:05:07.907Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
2024-11-06T09:05:07.907Z DEBUG 85138 --- [spring-graalvm-demo] [nio-8080-exec-1] tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
I suspect that this missing transaction is the cause of the problem.
Looking in the native image metadata that's generated during AOT processing, I don't see any entries for DomainUserDetailsService
. I would expect to see some as part of honouring its use of @Transactional
at runtime.
We'll transfer this issue to the Framework team so that they can continue the investigation.