Back to Blog
Fixing LazyInitializationException in Spring Boot: The Right Way
Java Engineering

Fixing LazyInitializationException in Spring Boot: The Right Way

The #1 error in JPA explained. Why open-in-view is bad, and how to use JOIN FETCH, @EntityGraph, and DTOs to fix it permanently.

January 14, 20263 min readRabi
javaspring-boothibernatejpaperformance

Itโ€™s 5:00 PM on a Friday. You deploy your code. You hit the API. 500 Internal Server Error.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.User.roles, could not initialize proxy - no Session

Every Java developer faces this.

Most people Google it, find a StackOverflow answer saying "Just set spring.jpa.open-in-view=true" or "change @OneToMany to FetchType.EAGER", and move on.

Please don't do that. You are solving the error but creating a massive performance bottleneck.

Lazy Initialization Error Timeline

Why does this happen?

Hibernate uses Proxies. When you load a User, it doesn't load their Roles list from the database immediately (that's Lazy loading). Instead, it puts a placeholder (Proxy) there.

  1. Service Layer (Transaction Open): You fetch the User. The Session is alive.
  2. Controller Layer (Transaction Closed): The transaction finishes. The Hibernate Session closes. The data is "Detached".
  3. JSON Serialization: Jackson tries to turn your User into JSON. It calls user.getRoles().
  4. Boom: The Proxy tries to call the database to load the roles, but the connection is gone. LazyInitializationException.

The Bad Fix: enable_lazy_load_no_trans

There is a Hibernate property called hibernate.enable_lazy_load_no_trans=true. Do not use this.

It tells Hibernate: "If the session is closed, just open a quick temporary new database connection to fetch this one field." If you are serializing a list of 100 users, this will open 100 tiny database connections. This is the N+1 Select Problem, and it will kill your database.

Solution 1: JOIN FETCH (JPQL)

The most robust solution is to tell Hibernate to fetch the data eagerly for this specific query, using JOIN FETCH.

public interface UserRepository extends JpaRepository<User, Long> {
    
    // โŒ Standard findById (Lazy)
    // Optional<User> findById(Long id);
 
    // โœ… Eager Fetch (Good)
    @Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id")
    Optional<User> findByIdWithRoles(@Param("id") Long id);
}

This generates one single SQL query that gets both User and Roles. No proxies. No errors.

Join Fetch Solution

Solution 2: @EntityGraph (The Clean Annotation)

If you don't like writing JPQL strings, Spring Data JPA provides @EntityGraph.

public interface UserRepository extends JpaRepository<User, Long> {
 
    @EntityGraph(attributePaths = {"roles", "address"})
    Optional<User> findById(Long id);
}

This does the exact same thing as JOIN FETCH but purely with annotations. You can even define named EntityGraphs on your entity class to reuse them.

Solution 3: DTO Projections (The Best Way)

Sometimes, you don't even need the Entity. If you are just building an API response, fetch a DTO (Data Transfer Object) directly.

Hibernate is smart enough to select only the columns you need.

public interface UserRepository extends JpaRepository<User, Long> {
 
    // Define a simple Java Record
    record UserSummary(String username, String roleName) {}
 
    @Query("SELECT new com.example.dto.UserSummary(u.username, r.name) " +
           "FROM User u JOIN u.roles r WHERE u.id = :id")
    List<UserSummary> findUserSummary(@Param("id") Long id);
}

Why this wins:

  1. Zero Lazy Loading issues: It's just a POJO/Record, not a Hibernate entity.
  2. Performance: You only select the 2 columns you need, instead of SELECT *.
  3. Safety: You can't accidentally trigger a LazyInitException because there are no proxies.

Summary

LazyInitializationException is not a bug; it's a feature protecting you from loading your entire database into memory.

  • Avoid OpenEntityManagerInView (it keeps DB connections open too long).
  • Use JOIN FETCH or @EntityGraph when you need the Entities.
  • Use DTO Projections for read-only API responses.

Fix it at the query level, not the config level.

Happy Coding! ๐Ÿš€

Join the Community

Get the latest articles on system design, frontend & backend development, and emerging tech trends delivered straight to your inbox.

No spam. Unsubscribe at any time.

Liked the blog?

Share it with your friends and help them learn something new!