For a greenfield PostgreSQL project today, my default is not "Hibernate everywhere." The ecosystem has shifted enough that I pick the persistence technology based on the application's query complexity rather than treating JPA as the default.
Here's roughly how I'd choose:
| Project characteristics | My default |
|---|
| CRUD-heavy business application | Spring Data JPA/Hibernate |
| Read-heavy app with complex SQL | jOOQ |
| Simple services with straightforward tables | Spring Data JDBC |
| Mixed application | JPA for writes + jOOQ for reporting/complex reads |
When I still choose JPA/Hibernate
I still think Hibernate is excellent when:
- the domain model is genuinely object-oriented
- relationships matter
- most operations are CRUD
- the team wants productivity over SQL control
The things Hibernate is really good at haven't changed:
- dirty checking
- cascading
- optimistic locking
- entity lifecycle
- lazy loading (when used carefully)
- reducing boilerplate
If 90% of your queries look like:
orderRepository.findByCustomerId(...)
then introducing jOOQ often just creates more work.
The downside is that once queries become analytical or Postgres-specific, Hibernate starts fighting you.
Why I've become a bigger fan of jOOQ
For PostgreSQL, jOOQ feels increasingly like the "native" way to work.
You get:
- compile-time checked SQL
- first-class CTEs
- window functions
- recursive queries
- JSONB support
- arrays
- PostGIS
- UPSERT
- lateral joins
- full control over generated SQL
Example:
dsl.select(
CUSTOMER.NAME,
count(ORDER.ID))
.from(CUSTOMER)
.join(ORDER)
.on(...)
.groupBy(CUSTOMER.NAME);
You know exactly what SQL will be executed.
No N+1 surprises.
No mysterious generated joins.
No wondering whether Hibernate decided to fetch half the database.
Spring Data JDBC
I like Spring Data JDBC much more than I did a few years ago.
It intentionally avoids:
- lazy loading
- proxies
- persistence context
- dirty checking
Each repository call simply executes SQL.
That simplicity is valuable.
The trade-off is that once aggregates become complicated, you'll write more SQL yourself.
It's great for:
- microservices
- event-driven systems
- simple aggregates
- teams that dislike ORM magic
The biggest shift
Ten years ago the recommendation was:
"Start with Hibernate and only use SQL when necessary."
Today my recommendation is closer to:
"Start with the amount of abstraction you actually need."
Modern PostgreSQL is incredibly capable. Features like JSONB, generated columns, CTEs, window functions, and advanced indexing are much easier to use when your persistence layer doesn't hide SQL.
The approach I personally find most practical
For many medium-to-large Spring applications:
- JPA/Hibernate for transactional writes and aggregate management.
- jOOQ for reporting, dashboards, searches, exports, and complex queries.
- Avoid forcing either tool into jobs it isn't well suited for.
This hybrid approach works surprisingly well because each library plays to its strengths.
If I had to pick just one today
If starting a brand-new PostgreSQL application:
- Simple CRUD/business app: Spring Data JPA.
- Data-intensive application: jOOQ.
- Performance-sensitive backend with lots of SQL: jOOQ.
- Simple microservice: Spring Data JDBC.
Compared with five or six years ago, I'm noticeably more willing to reach for jOOQ early. SQL has become a richer language, PostgreSQL has added powerful features, and jOOQ lets you use those capabilities without giving up type safety. Hibernate remains an excellent tool, but I no longer see it as the automatic default for every new project.