Hibernate Best Practices

Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.

Hibernate is by far the most popular JPA implementation. This popularity brings several advantages for all users. There are lots of blog posts about it, questions and answers on popular forums, and well-established best practices. In this post, I want to summarize some best practices for JPA and Hibernate which will help you to avoid common issues and to create better applications.

Best Practices

1. Use a projection that fits your use case

When you’re writing an SQL SELECT statement, you’re obviously only selecting the columns you need for your use case. And that shouldn’t be different when you work with Hibernate. Unfortunately, a lot of developers only select entities from the database whether or not it’s a good fit for the use case.

JPA and Hibernate support more projections than just entities. There are 3 different kinds of them, and each one has its advantages and disadvantages:

1.1 Entities

Entities are the most common projection. You should use it when you need all attributes of the entity and for update or delete operations that affect only a small number of entities.

em.find(Author.class, 1L);

1.2 POJOs

The POJO projection is similar to the entity projection but it allows you to create a use case specific representation of the database record. This is especially useful if you only need a small subset of the entity attributes or if you need attributes from several related entities.

List<BookPublisherValue> bookPublisherValues = em.createQuery(
  “SELECT new org.thoughts.on.java.model.BookPublisherValue(b.title, b.publisher.name) FROM Book b”,

1.3 Scalar values

Scalar values are not a very popular kind of projection because it presents the values as an Object[]. You should only use it if you want to select a small number of attributes and directly process them in your business logic. The POJO projection is most often the better option when you have to select larger numbers of attributes or if you want to transfer the query results to a different subsystem.

List<Object[]> authorNames = em.createQuery(
“SELECT a.firstName, a.lastName FROM Author a”).getResultList();

2. Use the kind of query that fits your use case

JPA and Hibernate offer multiple implicit and explicit options to define a query. None of them is a good fit for every use case, and you should, therefore, make sure to select the one that fits best.

2.1 EntityManager.find()

The EntityManager.find() method is not only the easiest way to get an entity by its primary key, but it also provides performance and security benefits:

  • Hibernate checks the 1st and 2nd level cache before it executes an SQL query to read the entity from the database.
  • Hibernate generates the query and sets the primary key value as a parameter to avoid SQL injection vulnerabilities.
em.find(Author.class, 1L);

2.2 JPQL

The Java Persistence Query Language is defined by the JPA standard and very similar to SQL. It operates on entities and their relationships instead of database tables. You can use it to create queries of low and moderate complexity.

TypedQuery<Author> q = em.createQuery(
  “SELECT a FROM Author a JOIN a.books b WHERE b.title = :title”,

2.3 Criteria API

The Criteria API is an easy API to dynamically define queries at runtime. You should use this approach if the structure of your query depends on user input. You can see an example for such a query in the following code snippet. If the title attribute of the input object contains a non-empty String, the Book entity gets joined to the Author entity and the title has to be equal to the input parameter.

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Author> q = cb.createQuery(Author.class);
Root<Author> author = q.from(Author.class);

if (!input.getTitle().isEmpty()) {
  SetJoin<Author, Book> book = author.join(Author_.books);
  q.where(cb.equal(book.get(Book_.title), input.getTitle()));

2.4 Native Queries

Native queries provide you the chance to write and execute plain SQL statements. This is often the best approach for highly complex queries and if you want to use database specific features, like PostgreSQLs JSONB data type.

MyEntity e = (MyEntity) em.createNativeQuery(
  “SELECT * FROM myentity e WHERE e.jsonproperty->’longProp’ = ‘456’“, 

I explain native queries in more detail in Native Queries – How to call native SQL queries with JPA and How to use native queries to perform bulk updates.

3. Use bind parameters

You should use parameter bindings for your query parameters instead of adding the values directly to the query String. This provides several advantages:

  • you do not need to worry about SQL injection,
  • Hibernate maps your query parameters to the correct types and
  • Hibernate can do internal optimizations to provide better performance.

JPQL, Criteria API, and native SQL queries use the same Query interface which provides a setParameter method for positional and named parameter bindings. Hibernate supports named parameter bindings for native queries but is not defined by the JPA specification. I therefore recommend only using positional parameters in your native queries. They are referenced as “?” and their numbering starts at 1.

Query q = em.createNativeQuery(“SELECT a.firstname, a.lastname FROM Author a WHERE a.id = ?”);
q.setParameter(1, 1);
Object[] author = (Object[]) q.getSingleResult();

Hibernate and JPA support named parameter bindings for JPQL and the Criteria API. This allows you to define a name for each parameter and provide it to the setParameter method to bind a value to it. The name is case-sensitive and needs to be prefixed with a “:” symbol.

Query q = em.createNativeQuery(“SELECT a.firstname, a.lastname FROM Author a WHERE a.id = :id”);
q.setParameter(“id”, 1);
Object[] author = (Object[]) q.getSingleResult();

4. Use static Strings for named queries and parameter names

This is just a small thing but it’s much easier to work with named queries and their parameters if you define their names as static Strings. I prefer to define them as attributes of the entities with which you can use them but you can also create a class that holds all query and parameter names.

@NamedQuery(name = Author.QUERY_FIND_BY_LAST_NAME,
query = “SELECT a FROM Author a WHERE a.lastName = :” + Author.PARAM_LAST_NAME)
public class Author {

  public static final String QUERY_FIND_BY_LAST_NAME = “Author.findByLastName”;
  public static final String PARAM_LAST_NAME = “lastName”;



You can then use these Strings to instantiate the named query and set the parameter.

Query q = em.createNamedQuery(Author.QUERY_FIND_BY_LAST_NAME);
q.setParameter(Author.PARAM_LAST_NAME, “Tolkien”);
List<Author> authors = q.getResultList();

5. Use JPA Metamodel when working with Criteria API

The Criteria API provides a comfortable way to define a query dynamically at runtime. This requires you to reference entities and their attributes. The best way to do that is to use the static JPA Metamodel. You can automatically generate a static metamodel class for each entity, at build time. This class contains a static attribute for each entity attribute.

@Generated(value = “org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor”)
public abstract class Author_ {

  public static volatile SingularAttribute<Author, String> firstName;
  public static volatile SingularAttribute<Author, String> lastName;
  public static volatile SetAttribute<Author, Book> books;
  public static volatile SingularAttribute<Author, Long> id;
  public static volatile SingularAttribute<Author, Integer> version;


You can then use the metamodel class to reference the entity attributes in the Criteria query. I use it in the 5th line of the following code snippet to reference the lastName attribute of the Author entity.

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Author> q = cb.createQuery(Author.class);
Root<Author> author = q.from(Author.class);
q.where(cb.equal(author.get(Author_.lastName), lastName));

I explain the JPA Metamodel and how you can generate its classes in Create type-safe queries with the JPA static metamodel.

6. Use surrogate keys and let Hibernate generate new values

The main advantage of a surrogate primary key (or technical ID) is that it is one simple number and not a combination of multiple attributes as most natural keys. All involved systems, mainly Hibernate and the database, can handle it very efficiently. Hibernate can also use existing database features, like sequences or auto-incremented columns, to generate unique values for new entities.

@Column(name = “id”, updatable = false, nullable = false)
private Long id;

7. Specify natural identifier

You should specify natural identifiers, even if you decide to use a surrogate key as your primary key. A natural identifier nevertheless identifies a database record and an object in the real world. A lot of use cases use them instead of an artificial, surrogate key. It is, therefore, good practice to model them as unique keys in your database. Hibernate also allows you to model them as a natural identifier of an entity and provides an extra API for retrieving them from the database.

The only thing you have to do to model an attribute is a natural id, is to annotate it with @NaturalId.

public class Book {

  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = “id”, updatable = false, nullable = false)
  private Long id;

  private String isbn;


You can read more about natural identifiers and Hibernate’s proprietary API in How to map natural IDs with Hibernate.

8. Use SQL scripts to create the database schema

Hibernate can use the mapping information of your entities to generate a database schema. That’s the easiest approach, and you can see it in several examples on the internet. That might be OK for a small test application, but you shouldn’t use it for a business application. The database schema has a huge influence on the performance and size of your database. You, therefore, should design and optimize the database schema yourself and export it as an SQL script. You can run this script either with an external tool like Flyway or you can use Hibernate to initialize the database at startup. The following snippet shows a persistence.xml file which tells Hibernate to run the create.sql script to setup the database. You can learn more about the different configuration parameters in Standardized schema generation and data loading with JPA 2.1.

<?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?>
<persistence xmlns=”http://xmlns.jcp.org/xml/ns/persistence” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” version=”2.1″ xsi:schemaLocation=”http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd”>
  <persistence-unit name=”my-persistence-unit” transaction-type=”JTA”>
    <description>My Persistence Unit</description>

      <property name=”hibernate.dialect” value=”org.hibernate.dialect.PostgreSQLDialect”/>

      <property name=”javax.persistence.schema-generation.scripts.action” value=”create”/>
      <property name=”javax.persistence.schema-generation.scripts.create-target” value=”./create.sql”/>

9. Log and analyze all queries during development

Too many executed queries are the most common reason for Hibernate performance problems. It is often caused by the n+1 select issue, but that’s not the only way to trigger more SQL statements than you expected.

Hibernate hides all database interactions behind its API, and it’s often difficult to guess how many queries it will perform for a given use case. The best way to handle this issue is to log all SQL statements during development and analyze them before you finish your implementation task. You can do that by setting the log level of the org.hibernate.SQL category to DEBUG.

I explain Hibernate’s most important log categories and provide detailed recommendations for a development and a production configuration in my Hibernate Logging Guide.

10. Don’t use FetchType.EAGER

Eager fetching is another common reason for Hibernate performance issues. It tells Hibernate to initialize a relationship when it fetches an entity from the database.

@ManyToMany(mappedBy = “authors”, fetch = FetchType.EAGER)
private Set<Book> books = new HashSet<Book>();

How Hibernate fetches the related entities from the database depends on the relationship and the defined FetchMode. But that’s not the main issue. The main issue is, that Hibernate will fetch the related entities whether or not they are required for the given use case. That creates an overhead which slows down the application and often causes performance problems. You should use FetchType.LAZY instead and fetch the related entities only if you need them for your use case.

@ManyToMany(mappedBy = “authors”, fetch = FetchType.LAZY)
private Set<Book> books = new HashSet<Book>();

11. Initialize required lazy relationships with the initial query

As I explained earlier, FetchType.LAZY tells Hibernate to fetch the related entities only when they’re used. This helps you to avoid certain performance issues. But it’s also the reason for the LazyInitializationException and the n+1 select issue which occurs when Hibernate has to perform an additional query to initialize a relationship for each of the selected n entities.

The best way to avoid both issues is to fetch an entity together with the relationships you need for your use case. One option to do that is to use a JPQL query with a JOIN FETCH statement.

List<Author> authors = em.createQuery(
  “SELECT DISTINCT a FROM Author a JOIN FETCH a.books b”,

I explain several other options and their benefits in 5 ways to initialize lazy relationships and when to use them.

12. Avoid cascade remove for huge relationships

Most developers (myself included) get a little nervous when they see a CascadeType.REMOVE definition for a relationship. It tells Hibernate to also delete the related entities when it deletes this one. There is always the fear that the related entity also uses cascade remove for some of its relationships and that Hibernate might delete more database records than intended. During all the years I’ve worked with Hibernate, this has never happened to me, and I don’t think it’s a real issue. But cascade remove makes it incredibly hard to understand what exactly happens if you delete an entity. And that’s something you should always avoid. If you have a closer look at how Hibernate deletes the related entities, you will find another reason to avoid it. Hibernate performs 2 SQL statements for each related entity: 1 SELECT statement to fetch the entity from the database and 1 DELETE statement to remove it. This might be OK, if there are only 1 or 2 related entities but creates performance issues if there are large numbers of them.

13. Use @Immutable when possible

Hibernate regularly performs dirty checks on all entities that are associated with the current PersistenceContext to detect required database updates. This is a great thing for all mutable entities. But not all entities have to be mutable. Entities can also map read-only database views or tables. Performing any dirty checks on these entities is an overhead that you should avoid. You can do this by annotating the entity with @Immutable. Hibernate will then ignore it in all dirty checks and will not write any changes to the database.

public class BookView {




I presented a broad range of best practices which help you to implement your application faster and to avoid common performance pitfalls. I follow them myself to avoid these issues, and they’ve helped me a lot.

Which best practices do you follow when working with JPA and Hibernate? Do you want to add something to the list? Please post a comment below and tell me about it.


  1. Avatar photo Anum Atique says:

    Hi, Thorben

    I have write simple flow that saves the data using JPA and sping boot application. But it is taking 900 ms in saving 1 transaction that is not acceptable in my case. Can you please help, how can I optimize it?

    1. Avatar photo Thorben Janssen says:

      Hi Anum,

      I would need more detailed information on which statements Hibernate executes to make any recommendations.


  2. Avatar photo kamboyamilan says:

    Nice article. Learned few things.

  3. The article is identical as a part of this one: [link removed to not give them any link juice …]
    One of them should mention the other as a source.

    1. Avatar photo Thorben Janssen says:

      Thank you for your message. That’s a pretty bold copy of my article.
      I reached out to them and asked them to remove the article.

  4. Avatar photo Mirek Hankus says:

    Hi Thorben,

    Immutable is a good hint, but it must be said that it is org.hibernate.annotations.Immutable and not javax.annotation.concurrent.Immutable – we’ve just finished fixing our code, because someone annotated entities with Immutable from javax concurrent.

  5. Avatar photo Vaibhav Mittal says:

    Hi Thorben,

    I am facing one unique situation , I have around 10 left outer joins in one of my query and number of records are 3.2 million
    If we choose any of the strategies that are based on JOIN , its taking 20 seconds for a batch size of 100 records because of high Cartesian product it seems , I am looking for a solution that can get rid of JOINS , and load associated entities without executing single query for each of them , I am basically doing pagination where I need 100 records on each page
    Does hibernate provide any solution for such use case , I have done lot of research on google , could not find a solution

    Please help

    Thanks ,
    Vaibhav Mittal

    1. Avatar photo Thorben Janssen says:

      Hi Vaibhav,

      I don’t think that there is any Hibernate feature that can solve this issue. It seems like your database can no longer handle the query efficiently if you add all the JOIN clauses. You need to rethink your domain model and query to check if you really need to model and fetch all these associations.
      If you can’t change your domain model, you might want to take a look at Hibernate’s FetchMode.BATCH. It initializes the associations in batches. To be honest, I don’t expect that this will fix your issues, but you can’t do much without changing your domain model.


  6. Avatar photo Udaib khan says:

    worth sharing post

  7. Thanks. Very helpful tips

  8. Nice article.
    I beg to differ with you on your opinion on the criteria API (“The Criteria API provides you an easy to API… “).
    I still find JPA’s criteria API one of the most awful API’s that I know of. Far from intuitive.

    1. Avatar photo Thorben Janssen says:

      I agree with you that the API isn’t beautiful. But after I used it a few times, it became surprisingly easy to use…

  9. Very good tips! Thank you!

  10. Avatar photo Prakash Venkatesh says:

    Thank you for the excellent collection of tips!

  11. Avatar photo Thành Loyal says:

    Thank you for your useful and interesting article!

  12. Avatar photo Kirill Vasin says:

    Thank you. Great article.

    As for me, I also trying to avoid @OneToOne relationships beacause of lack of lazy initialization

  13. Great Article, please write more like these articles on Java technologies

  14. Very Nice! Picked up some very usefull stuff.

    One thing in regards to parameter binding. From a security perspective you are considered a moron if you do not use it. So it is beyond a best practise IMO.

    1. Avatar photo Thorben Janssen says:

      Thanks for your comment, Peter.

      Unfortunately, I have seen too many queries that didn’t use parameter bindings. I will, therefore, repeat this recommendation again and again and again. 🙁

      1. As it should 🙂 Keep up the good work. Love your blog btw.

  15. Avatar photo Hans M. Rupp says:

    Thanks for your tips.
    What practices do you use for overriding toString(), equals() and hashCode() on entities?

  16. Avatar photo Binh Thanh Nguyen says:

    Thanks, nice tips.

  17. Avatar photo Howard Erickson says:


    Wanted to check out the mini course about finding and fixing n+1 select issues with Hibernate, but all I get is a link which just takes me to:

    Subscription Confirmed
    Thanks! Your subscription is confirmed.

    1. Avatar photo Thorben Janssen says:

      Hi Howard,

      you will receive an email with the first video, shortly after you confirmed your subscription.


  18. Avatar photo Luciano Fischer Lumertz says:

    Thanks for this article. They are very valuable tips.

    1. Avatar photo Thorben Janssen says:

      Thanks, Luciano.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.