Core Java

HibernateException: Illegal Collection Association Fix

In Hibernate, the exception IllegalAttemptToAssociateACollectionWithTwoOpenSessionsException occurs when a collection or entity is associated with two active Hibernate sessions simultaneously. This is a common problem when working with detached entities or reusing objects across sessions. Let us delve into understanding the Hibernate illegal association attempt of a collection to multiple Sessions issue and how to resolve it effectively.

1. Introduction

Hibernate is an Object-Relational Mapping (ORM) framework that helps developers manage database operations using Java objects. One of its core concepts is the session, which represents a unit of work with the database. Each session maintains a first-level cache, tracking changes to entities and collections that it manages. When you attempt to associate a persistent collection or entity with more than one active session, Hibernate is unable to reconcile the two session contexts. This leads to the following runtime exception:

org.hibernate.HibernateException: 
Illegal attempt to associate a collection with two open sessions

This exception commonly occurs in real-world applications under these scenarios:

  • Passing entities between multiple sessions without proper detachment: For example, fetching an entity in one session and trying to update it in another without detaching or merging it.
  • Using long-running sessions: When a session is kept open for a long time (such as in desktop apps or legacy systems), entities remain attached to the original session. Attempting to use them in a new session can trigger this exception.
  • Incorrect transaction boundaries: Transactions define the lifespan of a session’s persistence context. Failing to commit or close sessions at appropriate times can leave collections associated with multiple active sessions.

Understanding how Hibernate sessions, transactions, and entity states (transient, persistent, detached) work is crucial to avoiding this exception.

1.1 Entity States in Hibernate/JPA

Hibernate entities go through four lifecycle states: Transient, Persistent, Detached, and Removed. The table below explains each state with examples and notes.

StateDescriptionCode ExampleKey Points
TransientEntity not associated with any persistence context.
No DB identity yet.
Department dept = new Department();
dept.setName("Engineering"); 
// Transient: not saved, not tracked
  • No DB row exists yet.
  • Not tracked by Hibernate.
  • Becomes persistent after persist() or save().
Persistent (Managed)Entity is associated with an open persistence context.
Hibernate tracks changes and flushes automatically.
Department dept = new Department();
dept.setName("Engineering");
entityManager.persist(dept); 
dept.setName("Platform"); 
// INSERT + UPDATE on commit
  • Tracked by Hibernate (dirty checking).
  • Loaded via find()/get().
  • Flush/commit syncs to DB.
DetachedEntity was persistent but the context is closed or cleared.
Still has identity but not tracked anymore.
Department dept = em.find(Department.class, 1L);
em.close(); 
dept.setName("New Name"); 
// Detached: not tracked

Department managed = em.merge(dept); 
// 'managed' is persistent
  • No automatic synchronization.
  • Causes errors if directly reattached incorrectly.
  • Use merge() to reattach safely.
RemovedEntity is scheduled for deletion from DB.
Still managed until transaction commits.
Department dept = em.find(Department.class, 5L);
em.remove(dept); 
// DELETE on commit
  • Managed but marked for deletion.
  • DELETE issued on flush/commit.

2. Code Example

To better understand this problem, let’s consider a simple application with two entities: Employee and Department. The relationship between them is a One-to-Many association: one department can have multiple employees, and each employee belongs to one department. The entities are defined as follows:

// Employee.java
@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    private Department department;

    // getters and setters
}

// Department.java
@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    private List<Employee> employees = new ArrayList();

    // getters and setters
}

Here, the Department class owns a list of employees, and each Employee has a reference back to its Department. The cascade = CascadeType.ALL ensures that when we save or delete a department, the related employees are also affected automatically. Now, let’s simulate the problematic scenario where the exception occurs:

Session session1 = sessionFactory.openSession();
session1.beginTransaction();

// Step 1: Load department in the first session
Department dept = session1.get(Department.class, 1L);

session1.getTransaction().commit();
session1.close();  // Now the department is in a DETACHED state

// Step 2: Modify department
dept.setName("New Name");

// Step 3: Try attaching to a new session
Session session2 = sessionFactory.openSession();
session2.beginTransaction();

session2.update(dept);  
// Throws IllegalAttemptToAssociateACollectionWithTwoOpenSessionsException

session2.getTransaction().commit();
session2.close();

The exception occurs because dept.getEmployees() collection is still associated with the first session.

2.1 Why does this happen?

  • When dept was fetched in session1, its employees collection became associated with that session’s persistence context.
  • After closing session1, the dept object (and its collection) entered the detached state.
  • Calling update() on dept in session2 tries to reattach the same collection to a different session, which Hibernate disallows.
  • As a result, Hibernate throws IllegalAttemptToAssociateACollectionWithTwoOpenSessionsException.

3. Solutions

There are several approaches to fix this issue:

TitleDescriptionCode Fix
1Use merge() instead of update()The merge() method copies the state of the detached object into the persistent context of the new session.
Session session2 = sessionFactory.openSession();
session2.beginTransaction();
session2.merge(dept);  // Safe
session2.getTransaction().commit();
session2.close();
2Initialize collections before closing the sessionUse Hibernate.initialize() or fetch collections eagerly to avoid detached collection issues.
Hibernate.initialize(dept.getEmployees());
session1.getTransaction().commit();
session1.close();
3Use OpenSessionInView pattern (for web apps)This ensures a single session per request, reducing the chance of “Hibernate Illegal Attempt Collection Sessions” exceptions.
4Detach entities properlyDetach the entity from the first session before associating it with the next session.
session1.evict(dept);
session2.update(dept);
5Spring Boot Example with merge()This approach ensures Hibernate safely merges detached entities with the current persistence context.
@Service
public class DepartmentService {
  @Autowired
  private EntityManager entityManager;

  @Transactional
  public Department updateDepartment(Department dept) {
    return entityManager.merge(dept);
  }
}

4. Conclusion

The IllegalAttemptToAssociateACollectionWithTwoOpenSessionsException is a common Hibernate problem when working with detached entities or collections across multiple sessions. Understanding Hibernate session management and using methods like merge() or proper detachment strategies helps prevent this exception. By following best practices—such as maintaining clear session boundaries, initializing collections, and preferring merge()—developers can efficiently manage entity state and avoid runtime exceptions.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button