Resolving Hibernate LazyInitializationException: Failed to Lazily Initialize a Collection of Roles, Could Not Initialize Proxy - No Session

Nov 20, 2025 · Programming · 10 views · 7.8

Keywords: Hibernate | LazyInitializationException | Spring Security | Transaction Management | Lazy Loading

Abstract: This article provides an in-depth analysis of the Hibernate LazyInitializationException encountered in Spring Security custom AuthenticationProvider implementations. It explains the principles of lazy loading mechanisms and offers two primary solutions: using @Transactional annotation and FetchType.EAGER. The article includes comprehensive code examples and configuration guidelines to help developers understand and resolve this common issue effectively.

Problem Background and Error Analysis

In Spring Security framework, implementing custom AuthenticationProvider is a common extension requirement. However, when attempting to access associated collections of user entities, developers frequently encounter org.hibernate.LazyInitializationException. The core issue lies in the interaction between Hibernate's lazy loading mechanism and Spring's transaction management.

The error stack trace shows: failed to lazily initialize a collection of role: com.horariolivre.entity.Usuario.autorizacoes, could not initialize proxy - no Session. This indicates that when trying to access the autorizacoes collection, the Hibernate Session has already been closed, preventing the completion of lazy loading.

Hibernate Lazy Loading Mechanism Explained

Hibernate uses lazy loading strategy by default for collection types. This means when loading the Usuario entity from the database, the associated autorizacoes collection is not immediately loaded. Instead, a proxy object is returned. Hibernate only executes the SQL query to load the data when the collection is actually accessed.

In the provided code, the autorizacoes field in the Usuario entity class uses @LazyCollection(LazyCollectionOption.TRUE) annotation, explicitly specifying the lazy loading strategy:

@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(name = "autorizacoes_usuario", 
           joinColumns = { @JoinColumn(name = "fk_usuario") }, 
           inverseJoinColumns = { @JoinColumn(name = "fk_autorizacoes") })
@LazyCollection(LazyCollectionOption.TRUE)
public List<AutorizacoesUsuario> getAutorizacoes() {
    return this.autorizacoes;
}

Solution One: Using @Transactional Annotation

The most recommended solution is to add the @Transactional annotation to the authenticate method. This approach maintains the advantages of lazy loading while ensuring the Hibernate Session remains open throughout the method execution.

First, enable transaction management in Spring configuration:

<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven />

Then modify the CustomAuthenticationProvider class:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UsuarioHome usuario;

    public CustomAuthenticationProvider() {
        super();
    }

    @Override
    @Transactional
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        System.out.println("CustomAuthenticationProvider.authenticate");

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        Usuario user = usuario.findByUsername(username);

        if (user != null) {
            if(user.getSenha().equals(password)) {
                List<AutorizacoesUsuario> list = user.getAutorizacoes();

                List <String> rolesAsList = new ArrayList<String>();
                for(AutorizacoesUsuario role : list){
                    rolesAsList.add(role.getAutorizacoes().getNome());
                }

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                for (String role_name : rolesAsList) {
                    authorities.add(new SimpleGrantedAuthority(role_name));
                }

                Authentication auth = new UsernamePasswordAuthenticationToken(username, password, authorities);
                return auth;
            }
            else {
                return null;
            }
        } else {
            return null;
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

Advantages of this approach:

Solution Two: Using FetchType.EAGER

Another solution is to change the loading strategy of the association to eager loading. This can be achieved by modifying the fetch attribute of the @ManyToMany annotation:

@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "autorizacoes_usuario", 
           joinColumns = { @JoinColumn(name = "fk_usuario") }, 
           inverseJoinColumns = { @JoinColumn(name = "fk_autorizacoes") })
public List<AutorizacoesUsuario> getAutorizacoes() {
    return this.autorizacoes;
}

Characteristics of this method:

Comparison and Selection Between Two Solutions

@Transactional solution is more suitable for production environments because it:

FetchType.EAGER solution is appropriate for:

Best Practice Recommendations

In actual development, follow these best practices:

  1. Use Transaction Boundaries Appropriately: Apply @Transactional at the service layer methods to ensure complete business operations occur within the same transaction.
  2. Avoid Anti-Patterns: Do not use Open Session in View pattern or hibernate.enable_lazy_load_no_trans configuration, as these approaches lead to performance issues and hard-to-debug errors.
  3. Consider DTO Projections: For read-only operations, consider using DTO projections to avoid lazy loading issues with entity objects.
  4. Design Entity Relationships Reasonably: Design associations between entities based on business requirements, avoiding excessive use of either lazy or eager loading.

Code Optimization Suggestions

Beyond resolving LazyInitializationException, consider optimizing the original code:

@Override
@Transactional(readOnly = true)
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String username = authentication.getName();
    String password = authentication.getCredentials().toString();

    Usuario user = usuario.findByUsername(username);
    
    if (user == null || !user.getSenha().equals(password)) {
        return null;
    }

    List<GrantedAuthority> authorities = user.getAutorizacoes().stream()
        .map(autorizacao -> autorizacao.getAutorizacoes().getNome())
        .map(SimpleGrantedAuthority::new)
        .collect(Collectors.toList());

    return new UsernamePasswordAuthenticationToken(username, password, authorities);
}

This optimized version:

Conclusion

Hibernate LazyInitializationException is a common issue in Spring and Hibernate integration. By understanding the principles of lazy loading mechanisms and properly using the @Transactional annotation, this problem can be effectively resolved. In most cases, maintaining lazy loading strategy with proper transaction management is the optimal solution, as it preserves performance benefits while ensuring correct data loading.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.