A Hibernate SessionFactory is the object that holds everything Hibernate knows about our app, such as the database settings, the connection pool and the mapping of every entity, and it opens the Session objects that run our SQL. We build it once when the app starts and close it when the app stops.
We use the SessionFactory to open a short-lived Session for each unit of work, such as one web request or one transaction. For example, a task board app opens one session to save a new task and another session to list the open tasks, and both come from the same factory.
The following example builds a SessionFactory with HibernatePersistenceConfiguration, the Hibernate version of the Jakarta Persistence 3.2 PersistenceConfiguration class and the shortest way to build a factory in Hibernate 7.4.
SessionFactory sessionFactory = new HibernatePersistenceConfiguration("taskboard")
.managedClass(Task.class)
.jdbcUrl("jdbc:h2:mem:taskboard-jpa")
.jdbcCredentials("sa", "")
.schemaToolingAction(Action.CREATE_DROP)
.showSql(true, false, false)
.createEntityManagerFactory(); // returns a SessionFactory, no cast needed
sessionFactory.inTransaction(session ->
session.persist(new Task("Write release notes", "open", LocalDate.of(2026, 10, 9))));
// insert into Task (dueDate,status,title,id) values (?,?,?,?)
List<Task> tasks = sessionFactory.fromTransaction(session ->
session.createSelectionQuery("from Task", Task.class).getResultList());
// tasks = [Write release notes (open, due 2026-10-09)]
sessionFactory.close(); // drop table if exists Task cascade
Notice that createEntityManagerFactory() already returns a SessionFactory, and that inTransaction() and fromTransaction() open and close the session for us.
Next, we look at the steps Hibernate runs to build the factory and at each bootstrap API, from the native builders to hibernate.cfg.xml. The FAQs answer the questions about thread safety and getCurrentSession().
1. How Hibernate Builds a SessionFactory
Every bootstrap API runs the same four steps. First Hibernate collects the settings. After that it starts a service registry, the set of runtime services such as the connection pool and the Dialect (the class that writes SQL for our database). Next it reads the entity classes into metadata, the parsed mapping of classes to tables. From the registry and the metadata it builds the SessionFactory.

The bootstrap APIs differ only in how much of the four-step pipeline we see and where the settings come from.
| Way to build it | Main classes | Use it when |
|---|---|---|
| Jakarta Persistence 3.2 style | HibernatePersistenceConfiguration | New standalone code; the default choice |
| Native bootstrap | StandardServiceRegistryBuilder, MetadataSources | We need a hook into one step, such as statistics or a custom service |
| Configuration class | Configuration | Existing code that already uses it |
| Settings files | hibernate.properties, hibernate.cfg.xml | Settings should stay out of the Java code |
| From Jakarta Persistence | EntityManagerFactory.unwrap() | A persistence.xml file, Spring Boot or a Jakarta EE server creates the factory |
2. Building a SessionFactory Example
The following example is a team task board with a single Task entity that has a title, a status and a due date, stored in an in-memory H2 database. The complete project on GitHub builds the factory in each of the following ways and checks every result with 20 JUnit tests (mvn -q compile exec:java, mvn test).
2.1. The Task Entity and the Dependencies
The project needs hibernate-core and the JDBC driver of the database. Since Hibernate 6, we do not set hibernate.dialect or the driver class, because Hibernate detects both from the JDBC URL and the database.
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.4.11.Final</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.5.252</version>
</dependency>
The entity is a plain class with @Entity and an @Id. A LocalDate field maps to a SQL date column without extra annotations.
@Entity
public class Task {
@Id
@GeneratedValue
private Long id;
private String title;
private String status;
private LocalDate dueDate;
}
2.2. With HibernatePersistenceConfiguration
Jakarta Persistence 3.2 added PersistenceConfiguration, a builder that creates an EntityManagerFactory in Java code, without a persistence.xml file. Hibernate extends it with typed methods for common settings, and the Hibernate version of createEntityManagerFactory() returns a SessionFactory.
SessionFactory sessionFactory = new HibernatePersistenceConfiguration("taskboard")
.managedClass(Task.class) // entity classes
.jdbcUrl("jdbc:h2:mem:taskboard-jpa")
.jdbcCredentials("sa", "")
.schemaToolingAction(Action.CREATE_DROP) // create tables now, drop them at close()
.showSql(true, false, false) // show, format, highlight
.createEntityManagerFactory();
With CREATE_DROP, Hibernate creates the schema while it builds the factory.
Hibernate: create sequence Task_SEQ start with 1 increment by 50
Hibernate: create table Task (dueDate date, id bigint not null, status varchar(255), title varchar(255), primary key (id))
Any other setting goes through property(), for example .property(“hibernate.current_session_context_class”, “thread”).
2.3. Native Bootstrap With StandardServiceRegistryBuilder and MetadataSources
The native API gives us one builder per step of the pipeline. The StandardServiceRegistryBuilder takes the settings and starts the services, and MetadataSources reads the entity classes. Finally, the SessionFactoryBuilder that we get from the metadata builds the factory.
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.applySetting("jakarta.persistence.jdbc.url", "jdbc:h2:mem:taskboard-native")
.applySetting("jakarta.persistence.jdbc.user", "sa")
.applySetting("jakarta.persistence.jdbc.password", "")
.applySetting("hibernate.hbm2ddl.auto", "create-drop")
.applySetting("hibernate.show_sql", true)
.build();
try {
sessionFactory = new MetadataSources(registry)
.addAnnotatedClass(Task.class)
.buildMetadata()
.getSessionFactoryBuilder()
.applyStatisticsSupport(true) // a setting of the last step
.build();
} catch (Exception e) {
StandardServiceRegistryBuilder.destroy(registry); // release the registry if the build fails
throw e;
}
We can see that the extra code pays off when we need a hook into one step. For example, a team that wants to count the inserts of a batch job turns on statistics in the last step.
boolean enabled = sessionFactory.getStatistics().isStatisticsEnabled(); // true
sessionFactory.inTransaction(session ->
session.persist(new Task("Fix login bug", "open", LocalDate.of(2026, 10, 9))));
long inserts = sessionFactory.getStatistics().getEntityInsertCount(); // 1
The setting names are also available as constants, for example AvailableSettings.JAKARTA_JDBC_URL. When we need no builder options, buildMetadata().buildSessionFactory() is the short form.
2.4. With the Configuration Class
The Configuration class is the oldest bootstrap API, and it is still supported in Hibernate 7.4 and not deprecated. It collects settings and entity classes in one object and calls the native builders for us.
SessionFactory sessionFactory = new Configuration()
.addAnnotatedClass(Task.class)
.setProperty("jakarta.persistence.jdbc.url", "jdbc:h2:mem:taskboard-cfg")
.setCredentials("sa", "")
.setSchemaExportAction(Action.CREATE_DROP)
.showSql(true, false, false)
.buildSessionFactory();
Old tutorials call buildSessionFactory(serviceRegistry) with a registry built by hand, but the no-argument buildSessionFactory() does the same work.
2.5. With hibernate.properties or hibernate.cfg.xml
We can move the settings out of the Java code into a file on the classpath. Hibernate knows two file formats.
- The file hibernate.properties holds settings only.
- The file hibernate.cfg.xml holds settings and the list of entity classes.
jakarta.persistence.jdbc.url=jdbc:h2:mem:taskboard
jakarta.persistence.jdbc.user=sa
jakarta.persistence.jdbc.password=
hibernate.hbm2ddl.auto=create-drop
hibernate.show_sql=true
With hibernate.properties in place, the Java code only names the entity.
SessionFactory sessionFactory = new Configuration()
.addAnnotatedClass(Task.class)
.buildSessionFactory(); // url = jdbc:h2:mem:taskboard
The file hibernate.cfg.xml uses the same setting names inside property elements. Its mapping element replaces addAnnotatedClass().
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="jakarta.persistence.jdbc.url">jdbc:h2:mem:taskboard-xml</property>
<property name="jakarta.persistence.jdbc.user">sa</property>
<property name="jakarta.persistence.jdbc.password"></property>
<property name="hibernate.hbm2ddl.auto">create-drop</property>
<property name="hibernate.show_sql">true</property>
<mapping class="com.howtodoinjava.hibernate.sessionfactory.Task"/>
</session-factory>
</hibernate-configuration>
Hibernate reads hibernate.cfg.xml only when we call configure(). Both the Configuration class and the native builder have that method.
SessionFactory fromConfiguration = new Configuration()
.configure() // reads /hibernate.cfg.xml
.buildSessionFactory(); // url = jdbc:h2:mem:taskboard-xml
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure() // reads /hibernate.cfg.xml, mappings included
.build();
SessionFactory fromBuilder = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory(); // url = jdbc:h2:mem:taskboard-xml
The two files behave differently. Every bootstrap API reads hibernate.properties from the root of the classpath, even HibernatePersistenceConfiguration and persistence.xml units. Its values are defaults, and any other source overrides them.

2.6. Getting the SessionFactory From an EntityManagerFactory
The SessionFactory interface extends the Jakarta Persistence EntityManagerFactory interface, so any EntityManagerFactory that Hibernate creates is already a SessionFactory. The unwrap() call returns the same object with the Hibernate type.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("taskboard");
SessionFactory sessionFactory = emf.unwrap(SessionFactory.class);
boolean same = (sessionFactory == emf); // true
The standard PersistenceConfiguration works the same way when we want portable code without an XML file.
EntityManagerFactory emf = new PersistenceConfiguration("taskboard")
.managedClass(Task.class)
.property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:taskboard-unwrap")
.property(PersistenceConfiguration.JDBC_USER, "sa")
.property(PersistenceConfiguration.JDBC_PASSWORD, "")
.property(PersistenceConfiguration.SCHEMAGEN_DATABASE_ACTION, "drop-and-create")
.createEntityManagerFactory();
SessionFactory sessionFactory = emf.unwrap(SessionFactory.class);
The same rule holds one level down, so an EntityManager from this factory is a Hibernate Session, and em.unwrap(Session.class) returns it. In a Spring Boot application, the framework builds the EntityManagerFactory from the spring.datasource and spring.jpa properties, and the same unwrap() call gives us its SessionFactory.
2.7. Opening Sessions From the SessionFactory
A Session is the unit-of-work object. It tracks the entities we load and save in its persistence context and runs the SQL. The factory offers several ways to get a session.
| Method | What it does |
|---|---|
| inTransaction(session -> …) | Opens a session, begins a transaction, commits, closes |
| fromTransaction(session -> …) | Same, and returns the value of the lambda |
| openSession() | Opens a session that we close ourselves |
| getCurrentSession() | Returns the session bound to the current context (see FAQ 3.3) |
With openSession(), a try-with-resources block closes the session even when the code throws.
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.persist(new Task("Review PR", "open", LocalDate.of(2026, 10, 9)));
session.getTransaction().commit();
} // session.isOpen() = false
2.8. One SessionFactory per Application
Building a factory opens a connection pool, parses every entity and, with create-drop, creates the tables. Repeating that work for every request would waste time and connections, so an application builds one SessionFactory per database and shares it everywhere. Without a framework, a static final field holds the shared factory.
private static final SessionFactory SESSION_FACTORY = build();
private static SessionFactory build() {
SessionFactory sessionFactory = new HibernatePersistenceConfiguration("taskboard")
.managedClass(Task.class)
.jdbcUrl("jdbc:h2:mem:taskboard-app")
.jdbcCredentials("sa", "")
.schemaToolingAction(Action.CREATE_DROP)
.createEntityManagerFactory();
Runtime.getRuntime().addShutdownHook(new Thread(sessionFactory::close));
return sessionFactory;
}
public static SessionFactory sessionFactory() {
return SESSION_FACTORY;
}
The JVM runs build() once, the first time the class is used. The shared factory is thread-safe, so five virtual threads can each open their own session from it at the same time.
SessionFactory sessionFactory = TaskBoardDatabase.sessionFactory();
try (ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor()) {
for (String title : List.of("Plan sprint", "Fix login bug", "Update docs", "Review PR", "Deploy")) {
pool.submit(() -> sessionFactory.inTransaction(session ->
session.persist(new Task(title, "open", LocalDate.of(2026, 10, 9)))));
}
}
// select count(*) from Task -> 5
// sessionFactory == TaskBoardDatabase.sessionFactory() -> true

2.9. Closing the SessionFactory
The method close() releases the connection pool and the other services of the registry. With create-drop, it also drops the schema.
Hibernate: drop table if exists Task cascade
Hibernate: drop sequence if exists Task_SEQ
A closed factory refuses new sessions.
sessionFactory.close();
boolean closed = sessionFactory.isClosed(); // true
Session session = sessionFactory.openSession(); // IllegalStateException: EntityManagerFactory is closed
The SessionFactory interface extends AutoCloseable, so a factory used in one method, such as a command-line tool or a test, fits in a try-with-resources block. A long-running app either closes the factory in a shutdown hook, as TaskBoardDatabase does, or lets its framework close it.
3. SessionFactory FAQs
3.1. Is SessionFactory Thread-Safe?
Yes. The factory never changes after it is built, and any SessionFactory is threadsafe. A Session is the opposite, and a Session must never be shared between threads.
| SessionFactory | Session | |
|---|---|---|
| How many | One per database | One per request or unit of work |
| Thread-safe | Yes | No |
| Cost to create | High: pool, services, metadata | Low |
| Lives until | The application stops | The transaction ends |
| Jakarta Persistence type | EntityManagerFactory | EntityManager |
3.2. What Is the Difference Between SessionFactory and EntityManagerFactory?
The EntityManagerFactory is the Jakarta Persistence interface, and SessionFactory is Hibernate’s sub-interface of it. Both types point to the same object at runtime.
boolean isFactory = emf instanceof SessionFactory; // true
boolean same = emf.unwrap(SessionFactory.class) == emf; // true
boolean isSession = emf.createEntityManager() instanceof Session; // true
We use EntityManagerFactory for portable code, and SessionFactory when we need Hibernate features such as openStatelessSession(), getStatistics() or getCurrentSession().
3.3. What Is the Difference Between openSession() and getCurrentSession()?
The method openSession() always opens a new session that we close ourselves. The method getCurrentSession() returns a session bound to a context, and by default no context is configured.
org.hibernate.HibernateException: No CurrentSessionContext configured
The thread context binds one session to each thread, and that session closes itself when its transaction commits.
Session first = sessionFactory.getCurrentSession();
first.beginTransaction();
first.persist(new Task("Plan sprint", "open", LocalDate.of(2026, 10, 9)));
Session second = sessionFactory.getCurrentSession(); // second == first, same thread
first.getTransaction().commit();
boolean open = first.isOpen(); // false, closed by the commit
In new code we prefer inTransaction() and fromTransaction(), which open and close the session for us.
3.4. What Replaced AnnotationConfiguration and ServiceRegistryBuilder?
Both classes are gone from Hibernate 7.4, so loading them throws ClassNotFoundException. Their replacements are the bootstrap APIs from section 2.
| Old code | Hibernate 7.4 |
|---|---|
| new AnnotationConfiguration() | new Configuration(), or HibernatePersistenceConfiguration |
| new ServiceRegistryBuilder() | new StandardServiceRegistryBuilder() |
| configuration.buildSessionFactory(serviceRegistry) | configuration.buildSessionFactory() |
| javax.persistence.* imports | jakarta.persistence.* imports |
| hibernate.dialect, hibernate.connection.driver_class | Not needed; Hibernate detects both |
3.5. Can One Application Have Two SessionFactory Instances?
Yes, one for each database. Each factory has its own pool, metadata and second-level cache, so we keep each one in its own field and close both at shutdown. Several factories for the same database only use more connections and memory.
4. Conclusion
A SessionFactory is built once from settings, a service registry and the entity metadata. The class HibernatePersistenceConfiguration builds it in one call and is our default in Hibernate 7.4. The native builders give access to each step, whereas the Configuration class and hibernate.cfg.xml remain for existing code. When a framework creates the factory, unwrap() gets the SessionFactory from its EntityManagerFactory. We share one factory across threads, give each unit of work its own Session, and close the factory when the application stops.
5. References
- Hibernate ORM 7.4 Introduction: Configuration and bootstrap
- Hibernate ORM 7.4 User Guide: Native bootstrapping
- SessionFactory JavaDoc (Hibernate 7.4)
- HibernatePersistenceConfiguration JavaDoc (Hibernate 7.4)
- Configuration JavaDoc (Hibernate 7.4)
- PersistenceConfiguration JavaDoc (Jakarta Persistence 3.2)
- EntityManagerFactory JavaDoc (Jakarta Persistence 3.2)
Happy Learning !!