Bootstrap EntityManager in Hibernate 7: 5 Ways With JPA 3.2

Create one EntityManagerFactory per database in Hibernate 7.4, from persistence.xml, from Java code with PersistenceConfiguration or HibernatePersistenceConfiguration, or from a PersistenceUnitInfo, then open one EntityManager per unit of work with runInTransaction() and callInTransaction().

logo

To bootstrap an EntityManager in Hibernate, we first create one EntityManagerFactory for the database and then ask it for a new EntityManager for each unit of work. The factory is the expensive, thread-safe object that holds the settings, the connection pool and the entity mappings, whereas the EntityManager is the cheap, short-lived object that saves and loads entities inside a transaction.

We bootstrap Hibernate this way in every Java SE app without a framework, such as a batch job or an integration test. We can create the factory either from a persistence.xml file or from Java code with the Jakarta Persistence 3.2 PersistenceConfiguration API, and a framework creates it from a PersistenceUnitInfo.

The following example creates the factory once, with or without XML, and runs one unit of work per EntityManager.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("coffee-shop");   // reads META-INF/persistence.xml

// or, without XML
EntityManagerFactory emf = new PersistenceConfiguration("coffee-shop")
    .managedClass(Drink.class)
    .property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:menu-jpa")
    .createEntityManagerFactory();
emf.runInTransaction(em -> em.persist(new Drink("Latte", "small", "3.50")));
                                          // insert into Drink (name,price,size,id) values (?,?,?,?)

List<Drink> menu = emf.callInTransaction(em ->
    em.createQuery("from Drink order by price", Drink.class).getResultList());
                                          // menu = [Latte (small, 3.50)]

emf.close();                              // at application shutdown

Notice that runInTransaction() and callInTransaction() open and close the EntityManager for us, so the code never creates one by hand.

Next, we compare the two objects and the ways to create the factory. After that, we build each option for a small coffee shop menu, including the transaction helpers and the shutdown.

1. EntityManagerFactory vs EntityManager

Jakarta Persistence (JPA) splits the work between two objects. We build the EntityManagerFactory once, because building it reads every entity class, opens a connection pool and, if configured, creates the tables. We open an EntityManager per request or per transaction, and it keeps the entities it loads in its own persistence context, a map of the objects it tracks.

One shared EntityManagerFactory at the top creates three EntityManagers A, B and C for three requests: add a drink, show the menu and change a price; each EntityManager has its own persistence context and runs one transaction (begin, persist or select or update, commit) and is then closed; a red note says an EntityManager is not thread-safe and must not be shared between threads or kept in a static field
The factory is shared by every request. Each request gets its own EntityManager, runs its transaction and closes it.

The two objects have opposite rules.

EntityManagerFactoryEntityManager
How manyOne per database (persistence unit)One per request or unit of work
Thread-safeYesNo
Created byPersistence, PersistenceConfiguration or a frameworkemf.createEntityManager()
HoldsSettings, connection pool, entity metadataPersistence context and the current transaction
ClosedWhen the application stopsWhen the unit of work ends
Hibernate typeSessionFactorySession

An EntityManager must not be shared among concurrently executing threads. For example, a web app that keeps one EntityManager in a static field mixes the entities of different users’ requests. The factory’s methods, in contrast, are thread-safe, so we share one factory across the application.

1.1. Ways to Create the EntityManagerFactory

A persistence unit is a named set of entity classes plus the settings to reach one database. Every bootstrap option describes a persistence unit and hands it to the JPA provider, which for Hibernate is HibernatePersistenceProvider.

Four entry points on the left, three started by our code (META-INF/persistence.xml with Persistence.createEntityManagerFactory, PersistenceConfiguration from JPA 3.2, HibernatePersistenceConfiguration with typed methods) and one started by a framework (PersistenceUnitInfo plus DataSource with createContainerEntityManagerFactory); all arrows go to HibernatePersistenceProvider, found through META-INF/services in hibernate-core, which creates the EntityManagerFactory, a Hibernate SessionFactory
All four options end in the same provider and produce the same kind of factory. They differ only in where the settings come from.

The options differ in where the settings are and in whether the code stays portable to other JPA providers.

OptionSettings live inPortable to other JPA providersUse it when
Persistence.createEntityManagerFactory(name)META-INF/persistence.xmlYesSettings should stay out of the Java code
createEntityManagerFactory(name, map)persistence.xml plus a MapYesSome values (URL, password) are known only at runtime
PersistenceConfigurationJava codeYesWe want no XML and standard property names
HibernatePersistenceConfigurationJava codeNoWe want no XML and typed Hibernate methods
createContainerEntityManagerFactory()A PersistenceUnitInfo objectYes (SPI)We write a framework or container; applications rarely need it

2. Bootstrapping an EntityManager Example

The following example builds a coffee shop menu with Hibernate 7.4, Java 25 and an in-memory H2 database, and creates the factory in each way from the comparison table. The complete project on GitHub runs every step and prints the SQL, and its 16 JUnit tests check each result (mvn -q compile exec:java, mvn test).

2.1. The Drink Entity and the Dependency

The JPA provider is part of hibernate-core, so we no longer need the old hibernate-entitymanager artifact, and the H2 driver is the only other dependency.

<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>

A drink has a name, a size and a price. The @Entity and @Id annotations come from the jakarta.persistence package.

@Entity
public class Drink {

  @Id
  @GeneratedValue
  private Long id;

  private String name;          // "Latte"
  private String size;          // "small"
  private BigDecimal price;     // 3.50
}

2.2. With persistence.xml

The file persistence.xml declares one or more persistence units. The file must be at META-INF/persistence.xml on the classpath, which in a Maven project means src/main/resources/META-INF/persistence.xml.

<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
                                 https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
             version="3.2">

  <persistence-unit name="coffee-shop" transaction-type="RESOURCE_LOCAL">
    <class>com.howtodoinjava.hibernate.emf.Drink</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
      <property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:menu"/>
      <property name="jakarta.persistence.jdbc.user" value="sa"/>
      <property name="jakarta.persistence.jdbc.password" value=""/>
      <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
      <property name="hibernate.show_sql" value="true"/>
    </properties>
  </persistence-unit>

</persistence>

Four parts of the file matter most.

  • The attribute transaction-type=”RESOURCE_LOCAL” means our code controls the transactions through the JDBC connection. A Jakarta EE server uses JTA instead.
  • The class elements list the entities, and exclude-unlisted-classes stops Hibernate from adding other classes it finds.
  • The property schema-generation.database.action with drop-and-create drops and recreates the tables when the factory starts.
  • The provider element is optional. Hibernate registers HibernatePersistenceProvider in a META-INF/services file inside hibernate-core, and Persistence finds it there.

Persistence.createEntityManagerFactory() reads the file and builds the unit with the given name.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("coffee-shop");

String name = emf.getName();              // "coffee-shop"
PersistenceUnitTransactionType type = emf.getTransactionType();   // RESOURCE_LOCAL
Hibernate: drop table if exists Drink cascade
Hibernate: drop sequence if exists Drink_SEQ
Hibernate: create sequence Drink_SEQ start with 1 increment by 50
Hibernate: create table Drink (price numeric(38,2), id bigint not null, name varchar(255), size varchar(255), primary key (id))

2.3. Overriding persistence.xml Properties at Runtime

A second overload takes a Map of properties. Values in the map override the same keys in persistence.xml, and every key we leave out keeps the value from the file. A test or a production setup uses the override map to change the URL or the password without a second XML file.

Map<String, Object> overrides = Map.of(
    "jakarta.persistence.jdbc.url", "jdbc:h2:mem:menu-test",
    "hibernate.show_sql", "false");

EntityManagerFactory emf = Persistence.createEntityManagerFactory("coffee-shop", overrides);

The method getProperties() shows the merged result. Hibernate hides the user name and the password in this map.

Object url = emf.getProperties().get("jakarta.persistence.jdbc.url");    // "jdbc:h2:mem:menu-test"
Object showSql = emf.getProperties().get("hibernate.show_sql");          // "false", no SQL printed
Object user = emf.getProperties().get("jakarta.persistence.jdbc.user");  // "****"

2.4. Without XML Using PersistenceConfiguration

Jakarta Persistence 3.2 added PersistenceConfiguration, a builder that describes a persistence unit in Java code. It has the same parts as persistence.xml, and the class defines constants for the standard property names.

EntityManagerFactory emf = new PersistenceConfiguration("coffee-shop")
    .managedClass(Drink.class)
    .property(PersistenceConfiguration.JDBC_URL, "jdbc:h2:mem:menu-jpa")
    .property(PersistenceConfiguration.JDBC_USER, "sa")
    .property(PersistenceConfiguration.JDBC_PASSWORD, "")
    .property(PersistenceConfiguration.SCHEMAGEN_DATABASE_ACTION, "drop-and-create")
    .property("hibernate.show_sql", true)
    .createEntityManagerFactory();

emf.runInTransaction(em -> em.persist(new Drink("Flat White", "small", "3.90")));
                                          // insert into Drink (name,price,size,id) values (?,?,?,?)

This code uses only jakarta.persistence types, so it also works with another JPA provider. Its createEntityManagerFactory() calls the static Persistence.createEntityManagerFactory(PersistenceConfiguration), which we can also call ourselves.

2.5. With HibernatePersistenceConfiguration

HibernatePersistenceConfiguration extends PersistenceConfiguration with typed methods, so we do not need to remember property names. Its createEntityManagerFactory() returns a Hibernate SessionFactory.

SessionFactory emf = new HibernatePersistenceConfiguration("coffee-shop")
    .managedClass(Drink.class)
    .jdbcUrl("jdbc:h2:mem:menu-hibernate")
    .jdbcCredentials("sa", "")
    .jdbcPoolSize(5)                              // hibernate.connection.pool_size = 5
    .schemaToolingAction(Action.CREATE_DROP)      // create now, drop at close()
    .showSql(true, false, false)                  // show, format, highlight
    .createEntityManagerFactory();

EntityManager em = emf.createEntityManager();     // a Hibernate Session

A SessionFactory is an EntityManagerFactory, so the JPA code in the next sections works with it. The native Hibernate bootstrap API builds and tunes the SessionFactory without the JPA layer.

2.6. With PersistenceUnitInfo for Frameworks

Spring and Jakarta EE servers do not read persistence.xml through Persistence. They use the container contract of the specification instead. The framework fills a PersistenceUnitInfo object with the unit name, the entity class names, a DataSource and the properties, and passes it to the provider. Spring’s LocalContainerEntityManagerFactoryBean works this way.

public String getPersistenceUnitName() {
  return "coffee-shop-container";
}

public List<String> getManagedClassNames() {
  return List.of(Drink.class.getName());
}

public DataSource getNonJtaDataSource() {
  return dataSource;                      // connections come from here, not from a JDBC URL
}

public Properties getProperties() {
  return properties;                      // schema-generation action, hibernate.show_sql
}

PersistenceUnitInfo has 19 methods, and the others return null, empty lists or defaults in the project. The provider builds the factory from it.

JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:menu-container;DB_CLOSE_DELAY=-1");
dataSource.setUser("sa");

EntityManagerFactory emf = new HibernatePersistenceProvider()
    .createContainerEntityManagerFactory(new CoffeeShopUnitInfo(dataSource), Map.of());

String name = emf.getName();              // "coffee-shop-container"

In application code, we prefer PersistenceConfiguration, because it is one builder chain instead of a class with 19 methods. PersistenceUnitInfo helps us understand how a framework starts Hibernate when we debug how a framework starts Hibernate, or when we write one.

2.7. Running Work With runInTransaction() and callInTransaction()

With a factory in hand, each unit of work needs an EntityManager and a transaction. Jakarta Persistence 3.2 added two factory methods that do both.

  • The method runInTransaction(Consumer) runs code that returns nothing, such as em.persist().
  • The method callInTransaction(Function) runs code that returns a value, such as a query result.
emf.runInTransaction(em -> {
  em.persist(new Drink("Latte", "small", "3.50"));
  em.persist(new Drink("Cappuccino", "medium", "4.20"));
  em.persist(new Drink("Mocha", "large", "4.80"));
});                                       // 3 x insert into Drink (name,price,size,id) values (?,?,?,?)

List<Drink> menu = emf.callInTransaction(em ->
    em.createQuery("from Drink order by price", Drink.class).getResultList());
// select d1_0.id,d1_0.name,d1_0.price,d1_0.size from Drink d1_0 order by d1_0.price
// menu = [Latte (small, 3.50), Cappuccino (medium, 4.20), Mocha (large, 4.80)]

Both methods open the EntityManager, begin the transaction, run our lambda, commit and close. If the lambda throws an exception, the method rolls back the transaction, and the exception reaches the caller unchanged.

Flow of runInTransaction and callInTransaction: 1 create EntityManager, 2 begin transaction, 3 our lambda with em.persist, 4 commit and flush the SQL, 5 close and return the value; when the lambda throws, a red branch shows Out of beans with no SQL sent, rollback dropping the Espresso, then close and rethrow; a note says the EntityManager is closed in both cases and entities returned by callInTransaction are detached
The lambda decides the outcome. A normal return commits the transaction, and an exception rolls back the transaction. The EntityManager is closed either way.
try {
  emf.runInTransaction(em -> {
    em.persist(new Drink("Espresso", "small", "2.90"));
    throw new IllegalStateException("Out of beans");
  });
} catch (IllegalStateException e) {
  String message = e.getMessage();        // "Out of beans"
}
// select count(*) from Drink  -> 3, the Espresso was never inserted

The objects that callInTransaction() returns are detached entities. Their EntityManager is closed, so later changes to them are not saved. To save a change, we load the entity again in a new transaction or pass it to merge().

Drink latte = menu.get(0);
latte.setPrice(new BigDecimal("9.99"));   // no SQL, nobody tracks it
BigDecimal price = emf.callInTransaction(em -> em.find(Drink.class, latte.getId()).getPrice());   // 3.50

2.8. Managing the Transaction Ourselves

Before Jakarta Persistence 3.2 (Hibernate 6 and older), we wrote these steps by hand, and we still do when a unit of work needs more control, such as several transactions on one EntityManager. Since 3.2, EntityManager is AutoCloseable, so try-with-resources closes it.

try (EntityManager em = emf.createEntityManager()) {
  EntityTransaction tx = em.getTransaction();
  tx.begin();
  Drink latte = em.find(Drink.class, latteId);   // select ... from Drink d1_0 where d1_0.id=?
  latte.setPrice(new BigDecimal("3.75"));
  tx.commit();                                   // update Drink set name=?,price=?,size=? where id=?
}                                                // em.isOpen() = false

Without tx.commit(), the change never reaches the database. In real code we also call tx.rollback() in a catch block when tx.isActive() is still true; runInTransaction() does that for us.

2.9. One Factory per Application, One EntityManager per Unit of Work

Each EntityManager has its own persistence context. Inside one EntityManager, find() returns the same object for the same id; two EntityManager objects load two separate copies.

try (EntityManager first = emf.createEntityManager();
     EntityManager second = emf.createEntityManager()) {
  Drink a = first.find(Drink.class, latteId);    // select ... where d1_0.id=?
  Drink b = first.find(Drink.class, latteId);    // no SQL, read from the persistence context
  Drink c = second.find(Drink.class, latteId);   // select ... where d1_0.id=?
  // a == b -> true
  // a == c -> false
}

So we share the factory and not the EntityManager. Without a framework, we keep the factory in a static final field and close it in a shutdown hook. The CoffeeShopDatabase holder also uses the override map from section 2.3 to point to its own database.

private static final EntityManagerFactory EMF = build();

private static EntityManagerFactory build() {
  EntityManagerFactory emf = Persistence.createEntityManagerFactory("coffee-shop",
      Map.of("jakarta.persistence.jdbc.url", "jdbc:h2:mem:menu-app"));
  Runtime.getRuntime().addShutdownHook(new Thread(emf::close));
  return emf;
}

public static EntityManagerFactory emf() {
  return EMF;
}

Four virtual threads share that factory, and each runInTransaction() call gets its own EntityManager.

EntityManagerFactory shared = CoffeeShopDatabase.emf();
try (ExecutorService pool = Executors.newVirtualThreadPerTaskExecutor()) {
  for (String name : List.of("Latte", "Mocha", "Cortado", "Macchiato")) {
    pool.submit(() -> shared.runInTransaction(em -> em.persist(new Drink(name, "small", "3.60"))));
  }
}
// select count(*) from Drink  -> 4

2.10. Closing the EntityManagerFactory

The method close() releases the connection pool and the cached metadata, and after that both the factory and a closed EntityManager reject further work with an IllegalStateException.

em.close();
Drink drink = em.find(Drink.class, 1L);   // IllegalStateException: Session/EntityManager is closed

emf.close();
boolean open = emf.isOpen();              // false
EntityManager late = emf.createEntityManager();   // IllegalStateException: EntityManagerFactory is closed

EntityManagerFactory is AutoCloseable too, so a test or a command-line tool can create it in a try-with-resources block.

3. EntityManager Bootstrap FAQs

3.1. Why Do I Get “No Persistence provider for EntityManager named”?

Persistence.createEntityManagerFactory() did not find a persistence unit with that name. Here we passed “coffee” while the unit is called “coffee-shop”.

jakarta.persistence.PersistenceException: No Persistence provider for EntityManager named coffee

The same message appears in two common cases.

  • The name in the code does not match persistence-unit name in the file.
  • The file is not at META-INF/persistence.xml on the classpath, for example it sits in src/main/resources without the META-INF folder, so no unit is found at all.

3.2. How Do I Get an EntityManager in Spring?

In Spring and Spring Boot, the framework creates the EntityManagerFactory through LocalContainerEntityManagerFactoryBean (the PersistenceUnitInfo path from section 2.6), so we do not call any bootstrap code. We inject an EntityManager with @PersistenceContext, and @Transactional begins and commits the transaction.

@PersistenceContext
private EntityManager em;

@Transactional
public void addDrink(Drink drink) {
  em.persist(drink);
}

The injected object is a proxy that is safe to share, because for every transaction it forwards the calls to the EntityManager bound to the current thread.

3.3. What Is the Difference Between EntityManagerFactory and SessionFactory?

SessionFactory is Hibernate’s sub-interface of EntityManagerFactory, and Hibernate creates the same object for both. Every factory in section 2 is a SessionFactory, and every EntityManager is a Session.

boolean isFactory = emf instanceof SessionFactory;                      // true
boolean isSession = emf.createEntityManager() instanceof Session;       // true
SessionFactory sf = emf.unwrap(SessionFactory.class);

We write code against EntityManagerFactory for portability and unwrap the Hibernate type when we need Hibernate-only features such as statistics or a StatelessSession.

3.4. What Changed From the Old javax.persistence Setup?

Hibernate 6 moved to Jakarta Persistence, and Hibernate 7 implements version 3.2. Old tutorials still show the setup from the left column.

Old setupHibernate 7.4
javax.persistence.* importsjakarta.persistence.* imports
xmlns.jcp.org namespace, version=”2.1″jakarta.ee/xml/ns/persistence, version=”3.2″
javax.persistence.jdbc.url propertiesjakarta.persistence.jdbc.url properties
hibernate-entitymanager artifacthibernate-core only
org.hibernate.ejb.HibernatePersistence providerorg.hibernate.jpa.HibernatePersistenceProvider, or no provider element
getTransaction().begin() and commit() for every unit of workrunInTransaction(), callInTransaction()
Always persistence.xml in Java SEPersistenceConfiguration, no XML

3.5. Can I Use Several Persistence Units in One Application?

Yes. Each unit gets its own name in persistence.xml (or its own PersistenceConfiguration) and creates its own factory, with its own connection pool. In the project, the units “coffee-shop” and “coffee-shop-container” are open at the same time against two H2 databases, with 3 drinks in one and 1 in the other. We keep each factory in its own field and close all of them at shutdown.

4. Conclusion

An EntityManagerFactory is created once per database and shared, and an EntityManager is created for each unit of work and closed after it. In Hibernate 7.4, we create the factory from persistence.xml when settings belong in a file, or with PersistenceConfiguration or HibernatePersistenceConfiguration when they belong in code. Frameworks use PersistenceUnitInfo. For the unit of work itself, runInTransaction() and callInTransaction() replace the old begin, commit, rollback and close code.

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.