Hibernate SessionFactory: 5 Ways to Build It in Hibernate 7

Build a Hibernate SessionFactory in Hibernate 7.4 with HibernatePersistenceConfiguration, the native StandardServiceRegistryBuilder and MetadataSources API, the Configuration class, hibernate.cfg.xml, or unwrap() from an EntityManagerFactory. Then share one factory across threads and close it at shutdown.

Hibernate archetecture

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.

Bootstrap pipeline in four steps: settings such as the JDBC URL and show_sql go into a StandardServiceRegistry holding the connection pool and Dialect, MetadataSources parses Task.class into Metadata, SessionFactoryBuilder builds one SessionFactory, and the SessionFactory opens one Session per unit of work; a bottom band says HibernatePersistenceConfiguration, Configuration and PersistenceConfiguration run steps 1 to 4 in one call
The native API exposes one builder per step. The configuration classes run all four steps in a single call.

The bootstrap APIs differ only in how much of the four-step pipeline we see and where the settings come from.

Way to build itMain classesUse it when
Jakarta Persistence 3.2 styleHibernatePersistenceConfigurationNew standalone code; the default choice
Native bootstrapStandardServiceRegistryBuilder, MetadataSourcesWe need a hook into one step, such as statistics or a custom service
Configuration classConfigurationExisting code that already uses it
Settings fileshibernate.properties, hibernate.cfg.xmlSettings should stay out of the Java code
From Jakarta PersistenceEntityManagerFactory.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.

Three layers of settings, the higher one wins: settings in code such as jdbcUrl(), applySetting(), setProperty() or a Map passed to createEntityManagerFactory() gave taskboard-jpa, taskboard-code and taskboard-override; a file the bootstrap names, hibernate.cfg.xml read by configure() or persistence.xml read by Persistence.createEntityManagerFactory(), gave taskboard-xml and taskboard-emf; hibernate.properties, read by every bootstrap, gave taskboard only when no higher layer set the URL
hibernate.properties fills in what nothing else sets. A forgotten file there can change the settings of every factory in the application.

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.

MethodWhat 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
Lifecycle of the SessionFactory: at startup TaskBoardDatabase builds it once in a static final field, opening the pool and creating the tables; while running, three threads share the one SessionFactory and each opens its own Session A, B or C to save Plan sprint, Fix login bug and Update docs; at shutdown the hook calls close(), the pool closes and create-drop drops the Task table; below, building a factory per request and sharing a Session between threads are marked wrong, and openSession() after close() throws IllegalStateException
Threads share the factory, never a session. The factory lives as long as the application.

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.

SessionFactorySession
How manyOne per databaseOne per request or unit of work
Thread-safeYesNo
Cost to createHigh: pool, services, metadataLow
Lives untilThe application stopsThe transaction ends
Jakarta Persistence typeEntityManagerFactoryEntityManager

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 codeHibernate 7.4
new AnnotationConfiguration()new Configuration(), or HibernatePersistenceConfiguration
new ServiceRegistryBuilder()new StandardServiceRegistryBuilder()
configuration.buildSessionFactory(serviceRegistry)configuration.buildSessionFactory()
javax.persistence.* importsjakarta.persistence.* imports
hibernate.dialect, hibernate.connection.driver_classNot 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

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.