skip to content

Session vs EntityManager

The JPA facade and the native API underneath it: EntityManager vs Session, their factories, and when to unwrap. Interviewers ask to check you know which features are portable JPA and which are Hibernate extensions.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

questions

4

How does Hibernate's Session relate to JPA's EntityManager, and how would you reach Hibernate-only functionality when your code holds an EntityManager?

level: juniorimportance: must knowfreq 58%

answer

  1. Session extends EntityManager since 5.2
  2. em.unwrap(Session.class)
  3. one object, two interfaces, one persistence context
  4. SessionFactory extends EntityManagerFactory
  5. native extras: multiLoad, filters, natural id, StatelessSession

basics

~10 s

EntityManager is the standard JPA interface; Session is Hibernate's native implementation of it and a superset. Since Hibernate 5.2, Session extends EntityManager, so you reach the extras with entityManager.unwrap(Session.class).

solid answer

~50 s

`EntityManager` is the **JPA specification interface** — persist, merge, remove, find, getReference, createQuery, flush, transaction access. `Session` is **Hibernate's own API**, and since Hibernate 5.2 `Session extends EntityManager`, so every EntityManager method is available on a Session and the two are the same underlying object at runtime. When you hold an `EntityManager`, drop to the native API with: ```java Session session = em.unwrap(Session.class); ``` `unwrap` is itself standard JPA — the spec's escape hatch for provider-specific features — so the call compiles anywhere, though the type argument is Hibernate-specific. `em.getDelegate()` does something similar but returns `Object` and is less precise. What lives only on `Session`: `byId`/`byMultipleIds` multi-loading, `bySimpleNaturalId`, `enableFilter`, `setHibernateFlushMode` with Hibernate's own modes, `evict`, `lock` with Hibernate `LockMode`s, statistics-friendly helpers, and access to `StatelessSession` via the factory. Both share one persistence context, so mixing the two views in the same unit of work is safe.

code

java · 6 lines
java
Session session = em.unwrap(Session.class);
session.enableFilter("activeOnly").setParameter("flag", true);
List<Order> orders = session.byMultipleIds(Order.class).multiLoad(ids);

// same persistence context as the EntityManager
assert em.contains(orders.get(0));

go deeper

for a junior

Say clearly that EntityManager is the JPA interface, Session is Hibernate's implementation and superset, and unwrap(Session.class) bridges them.

for a middle

Add that Session extends EntityManager since 5.2, that the factories mirror the same relation, and name two or three native-only capabilities you have actually used.

for a senior

Discuss the coding convention — default to EntityManager, unwrap at narrow, documented points — and the portability drift caused by HQL-only syntax slipping into standard-looking queries.

for a principal

Treat it as a dependency-surface decision: how much provider-specific API you accept, where it is fenced, and what a provider or major-version change would actually cost.

## Two interfaces, one object JPA is a specification; Hibernate is one implementation of it. The specification's central runtime type is `EntityManager`: a handle on a persistence context plus the operations that move entities in and out of it. Hibernate's equivalent existed first and is called `Session`. From Hibernate 5.2 onward the two are formally related: `org.hibernate.Session extends jakarta.persistence.EntityManager` (`javax.persistence` before Jakarta EE 9). The consequence is that there is no adapter, no wrapper and no conversion cost — the object you hold as an `EntityManager` **is** a `Session`. `unwrap` is a downcast with a standard name. The same holds for the rest of the family: | JPA | Hibernate | |---|---| | `EntityManagerFactory` | `SessionFactory` (extends it) | | `EntityManager` | `Session` (extends it) | | `Query` / `TypedQuery` | `org.hibernate.query.Query` (extends them) | | `EntityTransaction` | `Transaction` | | `LockModeType` | `LockMode` | | JPQL | HQL (a superset) | ## How to unwrap ```java Session session = entityManager.unwrap(Session.class); SessionFactory sf = entityManagerFactory.unwrap(SessionFactory.class); ``` `unwrap(Class)` is defined by JPA and throws `PersistenceException` if the provider cannot supply that type — so on a different provider the code fails fast rather than silently misbehaving. The older `getDelegate()` returns `Object` and needs a cast; prefer `unwrap`. ## What you actually gain - **Multi-identifier loading**: `session.byMultipleIds(Order.class).multiLoad(ids)` fetches many rows in batched IN-list queries with cache integration — a big win over a loop of `find`. - **Natural id access**: `session.bySimpleNaturalId(User.class).load(email)` with its own resolution cache. - **Filters**: `session.enableFilter("activeOnly").setParameter(...)` applies a mapped predicate to every query and collection load for the session. - **Fine-grained flush control**: `session.setHibernateFlushMode(FlushMode.MANUAL)` — Hibernate's `MANUAL`/`ALWAYS` have no JPA equivalent. - **Cache management**: `session.evict(entity)` to detach a single instance (JPA's `detach` covers this) and `sessionFactory.getCache()` for region control. - **Locking with Hibernate's `LockMode`** including `UPGRADE_SKIPLOCKED` / `UPGRADE_NOWAIT` beyond the JPA `LockModeType` set. - **`StatelessSession`** from the factory: no persistence context, no dirty checking, no cascades — a direct-to-JDBC channel for bulk work. - **Statistics**: `sessionFactory.getStatistics()` for query counts and cache hit ratios. ## Which one should application code hold? The defensible default is to type your code to `EntityManager` and unwrap at the few points that need more. That keeps the bulk of the code against the standard interface — better documented, better known by new team members, and mockable in tests — while still giving you full access to Hibernate. Typing everything to `Session` is not wrong, just needless coupling for the 95% of calls that only use JPA operations. Two details worth having ready. First, **HQL versus JPQL**: JPQL is the standard subset, HQL adds Hibernate-only syntax and functions; a `Query` obtained from an EntityManager will still parse HQL because it is the same parser, which means portability drifts quietly. Second, **flush-mode vocabulary**: JPA has `AUTO` and `COMMIT`; Hibernate has `AUTO`, `COMMIT`, `MANUAL` and `ALWAYS`. Setting the Hibernate mode via `unwrap` is the only way to reach the extra two. ## Common confusion to correct Candidates sometimes describe Session and EntityManager as 'two different sessions' or claim you need to synchronise between them. There is one persistence context. Persisting through the unwrapped `Session` and reading through the `EntityManager` in the same unit of work sees exactly the same first-level cache and the same pending action queue — because it is one object wearing two interfaces.

  • If you persist through an unwrapped Session and then query through the EntityManager, do they see each other's work?
    Yes. They are the same object and therefore share one persistence context, one first-level cache and one action queue. A pending insert made through the Session is visible to a find through the EntityManager, and an auto-flush before a query will write it.
  • Name concrete capabilities that exist on Session but not on EntityManager.
    Multi-identifier loading via byMultipleIds().multiLoad, natural-id lookup via bySimpleNaturalId, session-scoped @Filter activation with enableFilter, Hibernate's MANUAL and ALWAYS flush modes, Hibernate LockModes such as UPGRADE_SKIPLOCKED, and access to StatelessSession and the SessionFactory statistics.
  • What is the difference between unwrap() and getDelegate()?
    unwrap(Class) is the typed JPA escape hatch: you ask for a specific provider type and get it, or a PersistenceException if the provider cannot supply it. getDelegate() returns an untyped Object whose actual class is provider-defined, so it requires a cast and gives no compile-time or fail-fast guarantee.

EntityManager is the standardised dashboard every car must have; Session is this manufacturer's dashboard, which has all the standard dials plus a few extra switches. unwrap is opening the panel to reach them.

saying these in an interview costs you the question

  • Describing Session and EntityManager as separate contexts that must be kept in sync
  • Thinking unwrap() creates a new session or copies state
  • Believing EntityManager is a wrapper object that adds a layer of indirection over Session
  • Assuming everything on Session is also on EntityManager, so unwrap is never needed

context

open as a page

What roles do Hibernate's SessionFactory and JPA's EntityManagerFactory play compared with a Session or EntityManager, and which of these objects are safe to share across threads?

level: middleimportance: must knowfreq 54%

basics

~20 s

The factory is a heavyweight, immutable, thread-safe, application-scoped object holding mappings, the connection pool, caches and query plans. A Session/EntityManager is cheap, short-lived, single-threaded and owns one persistence context. Never share a session between threads.

open as a page

In plain Hibernate, what is the difference between SessionFactory.openSession() and SessionFactory.getCurrentSession(), and what determines the scope and lifetime of a "current" session?

level: seniorimportance: should knowfreq 34%

basics

~20 s

openSession() always creates a new session that you must close yourself. getCurrentSession() returns the session bound to the current context — thread or JTA transaction, per hibernate.current_session_context_class — creating it on first call and closing it automatically at transaction end.

open as a page

When would you deliberately write code against Hibernate's native API instead of staying on the portable JPA API, and how do you stop that decision from spreading uncontrolled through a codebase?

level: principalimportance: should knowfreq 28%

basics

~20 s

Go native when JPA has no equivalent and the workaround is worse: StatelessSession bulk work, multiLoad, natural-id lookup, @Filter, custom types, Hibernate lock modes, statistics. Contain it behind narrow, documented seams rather than scattering unwrap() calls.

open as a page