Java Persistence Frameworks

Almost every Java application needs to save data to a database and read it back. Java persistence frameworks do the heavy lifting, so you aren't writing hundreds of lines of raw JDBC code. This guide explains the main options (Hibernate, JPA, Spring Data JPA, MyBatis, jOOQ, and more), shows the same query in each, and helps you pick the right one.

What is a Java persistence framework?

"Persistence" means data that outlives your program: it's still there after a restart because it's stored in a database. A persistence framework connects your Java objects to database tables. It's like a translator between two languages: your code speaks in objects (book.getTitle()), the database speaks in rows and SQL, and the framework converts between them.

Why not just use JDBC directly?

You can. JDBC is Java's built-in database API, and every framework below uses it underneath. But raw JDBC means opening connections, writing SQL strings, copying each column into a field, and closing everything carefully. Frameworks remove that repetition and many of the bugs that come with it.

ORM vs SQL-first: the two styles

ORM (object-relational mapping)SQL-first
IdeaMap classes to tables; the framework writes most SQL for youYou write SQL; the framework runs it and maps results
ExamplesHibernate, EclipseLink, Spring Data JPAMyBatis, jOOQ, Spring JdbcClient
StrengthsLess code for everyday work; database-independentFull control; predictable performance; any SQL feature
Watch out forHidden queries (the "N+1 problem"), a learning curveMore SQL to write and maintain

JPA vs Hibernate vs Spring Data JPA: what's the difference?

These three names confuse almost every beginner, because they're layers that work together:

A JPA entity is just a class with annotations telling the framework how it maps to a table:

import jakarta.persistence.*;

@Entity                          // this class maps to a table (named "book" by default)
public class Book {

    @Id
    @GeneratedValue              // let the database generate the id
    private Long id;

    private String title;

    @Column(name = "published_year")
    private int publishedYear;

    protected Book() { }         // JPA needs a no-argument constructor

    public Book(String title, int publishedYear) {
        this.title = title;
        this.publishedYear = publishedYear;
    }

    public String getTitle() { return title; }
    public int getPublishedYear() { return publishedYear; }
}
  • @Entity — marks the class as persistent. Each object becomes one row.
  • @Id and @GeneratedValue — the primary key, generated automatically.
  • @Column(name = "published_year") — maps a field to a differently named column.
  • jakarta.persistence — modern package name. Older code uses javax.persistence; the ideas are identical.

The main Java persistence frameworks

1. Hibernate ORM

The most widely used Java ORM and the most popular JPA implementation. It's the default persistence provider in both Spring Boot and Quarkus. Beyond standard JPA, it adds caching, batch processing, and many tuning options. hibernate.org

2. Spring Data JPA

Not an ORM itself, but the most common way Spring developers use one. Declare an interface such as BookRepository, and Spring generates methods like save, findById, and queries derived from method names. spring.io

3. MyBatis

A SQL mapper: you write the SQL (in annotations or XML files) and MyBatis maps the results to objects. Popular for complex queries, stored procedures, and databases designed long before the Java code. mybatis.org

4. jOOQ

Generates Java classes from your database schema so you can write SQL as type-safe Java code. If a column is renamed, your code stops compiling instead of failing in production. Free for open-source databases; commercial databases need a paid licence. jooq.org

Another full JPA implementation, originally the official reference implementation of JPA. It's the default in GlassFish and Payara application servers. Because both follow the JPA standard, entity code like the Book class above works with either. eclipse.dev/eclipselink

6. Spring JDBC (JdbcClient / JdbcTemplate)

A thin, friendly layer over JDBC with no ORM. You write SQL, and Spring handles connections, parameters, and errors. JdbcClient (Spring Framework 6.1+) is the modern fluent API; JdbcTemplate is the long-standing original.

Older options you may meet in existing code

  • Apache OpenJPA — another JPA implementation, historically used by Apache TomEE. Rarely chosen for new projects.
  • Apache Torque — an early ORM that generates classes from an XML schema. Mostly found in legacy applications.

The same query in 5 frameworks

Here's one task, "find all books published after 2000", written in each style. Switch between them:

Books published after 2000

JPA / Hibernate

Query objects with JPQL, which looks like SQL but uses class and field names. Hibernate translates it into SQL for your database.

List<Book> books = entityManager
    .createQuery("SELECT b FROM Book b WHERE b.publishedYear > :year", Book.class)
    .setParameter("year", 2000)
    .getResultList();

Spring Data JPA

Just declare a method. Spring reads its name and generates the query for you.

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByPublishedYearGreaterThan(int year);
}

// usage
List<Book> books = bookRepository.findByPublishedYearGreaterThan(2000);

MyBatis

You write the SQL; MyBatis runs it and maps each row to a Book.

@Mapper
public interface BookMapper {
    @Select("""
        SELECT id, title, published_year AS publishedYear
        FROM book WHERE published_year > #{year}
        """)
    List<Book> findNewerThan(int year);
}

jOOQ

SQL written as Java. BOOK is generated from your database schema, so a typo in a column name is a compile error.

List<BookRecord> books = dsl.selectFrom(BOOK)
    .where(BOOK.PUBLISHED_YEAR.gt(2000))
    .fetch();

Spring JdbcClient

Plain SQL with a friendly API (Spring Framework 6.1+). No ORM, no surprises.

List<Book> books = jdbcClient
    .sql("SELECT * FROM book WHERE published_year > :year")
    .param("year", 2000)
    .query(Book.class)
    .list();
JPA and Spring Data hide the SQL; MyBatis and JdbcClient show it plainly; jOOQ turns it into compiler-checked Java. All five end up sending almost the same SQL to the database.

Which Java persistence framework should you choose?

What fits your project?

Standard Spring Boot app

Use Spring Data JPA (with Hibernate underneath).

  • Repository interfaces with almost no code
  • The default, best-documented path in Spring Boot
  • Drop down to native SQL for the tricky queries

I want full control of SQL

Use MyBatis or jOOQ.

  • MyBatis: write SQL in annotations or XML, and it maps rows to objects
  • jOOQ: write SQL as type-safe Java code checked by the compiler
  • Great for complex reporting queries and legacy schemas

Small app, few tables

Use Spring JdbcClient (or plain JDBC).

  • No ORM concepts to learn: just SQL in, objects out
  • Very little hidden behaviour
  • Easy to move to JPA later if the app grows

Jakarta EE / Quarkus

Use Jakarta Persistence (JPA) via Hibernate, or Hibernate with Panache in Quarkus.

  • Standard annotations that work across servers
  • GlassFish and Payara use EclipseLink instead; your JPA code stays the same
When in doubt in a Spring Boot project, start with Spring Data JPA. You can always add MyBatis, jOOQ, or JdbcClient for the queries that need more control.

Comparison table

FrameworkStyleYou write SQL?Learning curveBest for
HibernateORM (JPA)RarelyMedium–HighRich domain models, most business apps
Spring Data JPAORM layerRarelyLow to startSpring Boot CRUD and typical apps
MyBatisSQL mapperYesLowComplex SQL, legacy schemas
jOOQType-safe SQLYes (as Java)MediumSQL-heavy apps wanting compile-time safety
EclipseLinkORM (JPA)RarelyMedium–HighJakarta EE servers (GlassFish, Payara)
Spring JdbcClientThin JDBCYesVery lowSmall apps, simple queries

The N+1 problem: with an ORM, looping over 100 orders and touching each order's customer can quietly run 101 queries. Learn to spot it in your SQL logs and fix it with a "fetch join" or an entity graph. It's the most common ORM performance issue.

Quick recap

  • Persistence frameworks save Java objects to databases without raw JDBC boilerplate.
  • JPA is the standard, Hibernate implements it, and Spring Data JPA adds ready-made repositories on top.
  • ORMs (Hibernate, EclipseLink) write SQL for you; MyBatis, jOOQ, and JdbcClient let you control the SQL.
  • Default for Spring Boot: Spring Data JPA. Need SQL control: MyBatis or jOOQ.

What's next?

Pick the web layer to go with your data layer in Java web frameworks, or turn your data into PDFs and dashboards with Java reporting frameworks.

Frequently Asked Questions

What is a persistence framework in Java?

A persistence framework is a library that saves Java objects to a database and loads them back, so you don't write all the low-level JDBC code yourself. Some, like Hibernate, map objects to tables automatically (ORM); others, like MyBatis and jOOQ, help you write and run SQL more safely.

What is the difference between JPA and Hibernate?

JPA (now called Jakarta Persistence) is a specification: a standard set of annotations and interfaces like @Entity and EntityManager. Hibernate is an implementation of that specification, the actual library that does the work. You code against JPA, and Hibernate runs it underneath.

Is Spring Data JPA the same as Hibernate?

No. Spring Data JPA sits on top of JPA and removes repetitive code: you declare a repository interface and Spring writes the queries for you. Underneath, it still needs a JPA implementation, which is Hibernate by default in Spring Boot.

Which Java persistence framework is best?

For most Spring Boot applications, Spring Data JPA with Hibernate is the standard choice. If you want full control over SQL, choose MyBatis or jOOQ. For simple data access without an ORM, Spring's JdbcClient is lightweight and easy to understand.

Should I use an ORM or write SQL myself?

An ORM saves a lot of code for typical create-read-update-delete work and keeps your model object-oriented. Hand-written SQL gives predictable performance and full use of database features. Many teams mix both: an ORM for everyday work and SQL for complex reports.