spring-framework icon indicating copy to clipboard operation
spring-framework copied to clipboard

LazyInitializationException with GraalVM Native Image in Spring Boot 3.3.5 Application

Open susimsek opened this issue 1 year ago • 4 comments

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

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

  1. Clone the repository spring-graalvm-demo.

  2. Configure the project for native image generation with GraalVM.

  3. Run the native image build with the following command:

    ./mvnw native:compile -Pnative
    
  4. 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.

susimsek avatar Nov 02 '24 08:11 susimsek

@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.

snicoll avatar Nov 03 '24 04:11 snicoll

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.

susimsek avatar Nov 03 '24 11:11 susimsek

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.

wilkinsona avatar Nov 06 '24 09:11 wilkinsona

I am experiencing a similar issue. I’m following up on it.

The related question: https://stackoverflow.com/questions/79597487/spring-boot-3-4-4-native-build-error-finding-reachability-metadata-json-with-na

Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Classes cannot be defined at runtime when using ahead-of-time Native Image compilation. Tried to define class 'com.test.api.models.attribute.entity.Attribute$HibernateProxy$fn2d7u5R'

And this is my DbTransactionConfig class:

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.test.api"})
@EnableJpaRepositories(basePackages = {"com.test.api"},
        entityManagerFactoryRef = "apiEntityManagerFactory",
        transactionManagerRef = "apiTransactionManager")
@Profile("api")
public class DbTransactionConfig {

    @Autowired
    DataBaseUtil dataBaseUtil;

    @Bean
    @ConfigurationProperties(prefix = "db.api.datasource", ignoreInvalidFields = true)
    public DatabaseConfigProperties apiDataSourceConfig() {
        return new DatabaseConfigProperties();
    }

    @Bean(name = "apiDataSource")
    @LiquibaseDataSource
    public DataSource apiDataSource() {
        log.info("Setup of apiDataSource succeeded.");
        return dataBaseUtil.createAndConfigureDataSource(apiDataSourceConfig());
    }

    @Primary
    @Bean(name = "apiEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean apiEntityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(apiDataSource());
        em.setPackagesToScan("com.test.api");
        em.setPersistenceUnitName("apidb-persistence-unit");
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        em.setJpaPropertyMap(dataBaseUtil.getHibernateProperties());
        log.info("Setup of apiEntityManagerFactory succeeded.");
        return em;
    }

    @Bean(name = "apiTransactionManager")
    public JpaTransactionManager apiTransactionManager(@Qualifier("apiEntityManagerFactory") EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        log.info("Setup of apiTransactionManager succeeded.");
        return transactionManager;
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        log.info("Setup of exceptionTranslation succeeded.");
        return new PersistenceExceptionTranslationPostProcessor();
    }
}

Java with GraalVM: Oracle GraalVM 24.0.1+9.1 native-maven-plugin: 0.10.6 Spring Boot: 3.4.4 Hibernate-Core,Hibernate-GraalVM : 6.6.13.Final

hanbeytoktas avatar Apr 29 '25 14:04 hanbeytoktas