LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2521 min read

Common Hibernate Exceptions & Debugging

Learn how to recognize and fix the Hibernate exceptions you will hit most often — LazyInitializationException, NonUniqueObjectException — and how to enable SQL logging to debug.

Introduction

By this point in the course you have touched most of Hibernate's core machinery — sessions, transactions, caching, and lazy loading. This lesson is about the exceptions those pieces throw when something goes wrong, and how to read Hibernate's SQL logs to figure out why.

What You Will Learn
  • Why LazyInitializationException happens and how to fix it
  • What causes NonUniqueObjectException
  • What StaleObjectStateException means for optimistic locking
  • How to enable Hibernate SQL logging to debug queries
  • A practical checklist for diagnosing Hibernate errors

LazyInitializationException

This is the exception nearly every Hibernate developer hits first. It happens when you try to access a lazily-loaded association after the Session that loaded the owning entity has already closed.

Employee emp;
try (Session session = factory.openSession()) {
emp = session.get(Employee.class, 1L);
} // Session closes here
System.out.println(emp.getProjects().size()); // BOOM
Exception

Click Run to see what this code prints.

getProjects() is mapped @OneToMany(fetch = FetchType.LAZY), so accessing it triggers a fresh query — but there is no open Session left to run that query through.

NonUniqueObjectException

This one happens when you try to attach two different Java object instances that represent the same database row (same id) to the same Session at once. Hibernate refuses because it cannot track two objects as one identity.

Session session = factory.openSession();
Employee emp1 = session.get(Employee.class, 1L);
Employee emp2 = new Employee();
emp2.setId(1L);
emp2.setName("Different Name");
session.update(emp2); // BOOM
Exception

Click Run to see what this code prints.

One Session, One Identity

Within a single Session, Hibernate guarantees at most one managed instance per entity identity. If you need to work with a detached copy that has the same id, merge() it instead of update()-ing it directly.

StaleObjectStateException

This is thrown when optimistic locking (typically via an @Version field) detects that a row was modified by someone else between when you read it and when you tried to save your change.

@Entity
public class Account {
@Id
private Long id;
private Double balance;
@Version
private Integer version;
}
Exception (when a concurrent update wins the race)

Click Run to see what this code prints.

This is the mechanism working correctly, not a bug — it is telling you two transactions tried to modify the same row concurrently, and one of them needs to retry with fresh data rather than silently overwrite the other's change.

LazyInitializationException Fixes

There are several standard fixes, and which one is right depends on the situation.

FixHow
Access the association before closingCall emp.getProjects().size() while the Session is still open
Use JOIN FETCHFetch the association eagerly in the query itself, covered in the N+1 lesson
Use Hibernate.initialize()Explicitly force initialization: Hibernate.initialize(emp.getProjects())
Open Session In ViewA web-layer pattern that keeps the Session open through view rendering (use cautiously)
Change to EAGER fetchingOnly for associations that are always needed — otherwise this reintroduces N+1 risk
Fixing it with JOIN FETCH
Employee emp = session.createQuery(
"SELECT e FROM Employee e JOIN FETCH e.projects WHERE e.id = :id", Employee.class)
.setParameter("id", 1L)
.getSingleResult();
System.out.println(emp.getProjects().size()); // Safe — already loaded

Enabling SQL Logging

Most Hibernate debugging starts with seeing the actual SQL being generated. Two properties turn that on.

hibernate.cfg.xml
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.use_sql_comments">true</property>
application.properties (Spring Boot)
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
Formatted SQL Log Example

Click Run to see what this code prints.

The First Debugging Step

Before assuming a bug is in your Java logic, turn on SQL logging and look at what Hibernate is actually sending to the database. A surprising number of "wrong result" bugs turn out to be an unexpected query shape, not bad application code.

Common Mistakes

Avoid These Mistakes
  • Trying to "fix" LazyInitializationException by switching every association to EAGER, which trades one problem for N+1 queries and overfetching.
  • Ignoring StaleObjectStateException instead of retrying the operation with fresh data.
  • Calling update() on a detached object with an id that matches an already-managed instance in the same Session.
  • Debugging blind without ever turning on SQL logging.
  • Leaving hibernate.show_sql enabled in production, where it adds noise and overhead.

Best Practices

  • Enable SQL logging in development environments as a default, not just when something breaks.
  • Fetch lazy associations deliberately with JOIN FETCH or Hibernate.initialize() rather than defaulting to EAGER everywhere.
  • Treat StaleObjectStateException as a signal to reload and retry, not an error to suppress.
  • Use merge() rather than update() when reattaching a detached object that might already be represented in the Session.
  • Read the full exception message and stack trace — Hibernate's exceptions usually name the exact entity and association involved.

Frequently Asked Questions

Because the entity itself loaded successfully, but the lazy association was only replaced with a proxy — the actual data is fetched on first access, which requires an open Session.

It solves the symptom but can hide N+1 problems and keep database connections open longer than necessary. Many teams prefer explicit JOIN FETCH instead.

No — it means Hibernate prevented a potential lost update by refusing to overwrite a row that changed since you read it. You typically reload and retry.

Set hibernate.show_sql (or spring.jpa.show-sql in Spring Boot) to true, and enable hibernate.format_sql for readability.

Key Takeaways

  • LazyInitializationException means an association was accessed after its Session closed.
  • NonUniqueObjectException means two different objects with the same id were attached to one Session.
  • StaleObjectStateException signals a concurrent update conflict caught by optimistic locking.
  • JOIN FETCH and Hibernate.initialize() are the standard fixes for lazy loading outside a Session.
  • SQL logging (show_sql / format_sql) is the first debugging tool to reach for.

Summary

Most Hibernate exceptions map directly to concepts you already know — Session lifecycle, entity identity, and optimistic locking. Recognizing the pattern behind each one, and knowing how to turn on SQL logging to see what Hibernate is actually doing, turns a confusing stack trace into a quick fix.

Next Lesson →

Performance Tips (The N+1 Problem)