skip to content

Hibernate's HQL is a superset of the Jakarta Persistence query language — what concrete capabilities does HQL add beyond the specification, and what do you give up by using them?

level: seniorimportance: nice to knowfreq 32%

answer

  1. Every JPQL query is valid HQL, not vice versa
  2. limit/offset, union/intersect, window fns, CTEs
  3. Derived roots + ad-hoc entity joins
  4. Dialect function registry, no FUNCTION escape
  5. Cost = provider lock-in + Criteria gap

basics

~20 s

HQL adds an explicit limit/offset clause, set operations, window functions, common table expressions, subqueries in FROM, ad-hoc entity joins, a much larger function library, and shortcuts like omitting SELECT. The cost is provider lock-in: those queries only run on Hibernate.

solid answer

~60 s

Every JPQL query is valid HQL; HQL then goes further. The main additions in Hibernate 6: - **`limit` / `offset` / `fetch first`** directly in the query text, instead of `setMaxResults`. - **Set operations**: `union`, `union all`, `intersect`, `except`. - **Window functions and ordered-set aggregates** (`over (partition by ...)`, `listagg`), plus richer aggregate syntax such as `filter`. - **Common table expressions**, including recursive ones. - **Subqueries in the FROM clause** (derived roots) and ad-hoc joins to unrelated entities with an ON condition. - **A large registered function library** per dialect — `cast`, `extract`, date arithmetic, string, JSON and array functions — callable by name without the `FUNCTION` escape. - **Syntactic conveniences**: `from Book b` with no select clause, `id()`, `version()`, `type()`, tuple comparisons, literal enum references. What you give up is portability of the persistence provider — not of the database, since Hibernate still renders per-dialect SQL. Several of these features also postpone errors to execution and are not expressible in the Criteria API, so keep them deliberate and tested.

code

java · 8 lines
java
session.createQuery("""
    select b.title,
           rank() over (partition by b.category order by b.price desc) as r
      from Book b
     where b.published = true
     order by b.category, r
     limit 50
    """, Object[].class).getResultList();

go deeper

for a junior

Know that HQL is Hibernate's language and that JPQL is the portable subset it implements.

for a middle

Name a few concrete additions such as the limit clause, set operations and richer functions, and state the provider-portability cost.

for a senior

Argue when HQL beats a native query, note the Criteria gap and upgrade risk, and require tests around HQL-only syntax.

for a principal

Make it a deliberate architectural stance: how much provider portability the project actually retains, where vendor-shaped queries are allowed to live, and how upgrades are protected.

## The relationship JPQL is the query language defined by the Jakarta Persistence specification. HQL is Hibernate's own language, and Hibernate implements JPQL as a subset of it. Practically: paste any JPQL into `em.createQuery` on Hibernate and it works; write HQL-only syntax and it works on Hibernate and nowhere else. Hibernate 6 rewrote the query engine (a semantic query model compiled to SQL AST), which both tightened spec compliance and expanded HQL substantially — so "what HQL adds" is a version-sensitive question and the modern answer is much longer than it was in Hibernate 5. ## The concrete additions **Row limiting in the query text.** `from Book b order by b.price desc limit 10` or the ANSI `fetch first 10 rows only`, plus `offset`. JPQL has no such clause; you must call `setMaxResults`/`setFirstResult`. Useful when the limit is intrinsic to the query rather than to the call site. **Set operations.** `union`, `union all`, `intersect`, `except` between full queries. JPQL has none, so the portable workaround is two queries merged in Java. **Window functions.** `select b.title, rank() over (partition by b.categoryId order by b.price desc) from Book b`. This is the feature most likely to pull a team from native SQL back into HQL: ranking, running totals, `lag`/`lead`, and `filter (where ...)` on aggregates. **CTEs.** `with recent as (select ...) select ... from recent ...`, including `with recursive` for tree traversal — the ORM answer to hierarchical queries that previously forced a native query. **Derived roots.** A subquery in the FROM clause, joined with an ON condition, which is how you express "the latest row per group" without a correlated subquery per column. **Ad-hoc entity joins.** `join Ledger l on l.code = a.code` between entities with no mapped association. Long a Hibernate-only feature (since 5.1); Jakarta Persistence 3.2 has since standardised it, which is a nice illustration of how HQL features migrate into the spec over time. **Function library.** Hibernate registers a wide set per dialect — `cast(x as ...)`, `extract(year from d)`, `year()`, `format()`, string padding, date arithmetic, JSON and array operations in recent versions — callable by plain name. Portable JPQL would require `FUNCTION('...')` with the vendor's own name. **Conveniences.** Omitting the select clause (`from Book b`); `id(b)`, `version(b)`, `type(b)` accessors; comparing tuples `(a, b) = (?1, ?2)`; referring to enum constants by simple name; more permissive implicit joins and ON-clause contents. (Bulk statements also carry Hibernate-specific extensions, but those belong to the bulk-operations discussion rather than to language fundamentals.) ## What using HQL costs **Provider portability.** The realistic question is not "could we swap Hibernate for EclipseLink?" — few teams ever do — but whether the codebase is honest about the dependency. A project that already uses Hibernate-specific mappings, `@Filter`, or the Hibernate `Session` has no portability left to protect; one that has kept strictly to the spec should decide consciously before spending it. **Tooling and typing.** HQL-only constructs are strings checked at bootstrap (for named queries) or at execution (for dynamic ones). Several have no Criteria API equivalent, so a codebase that builds dynamic queries with Criteria cannot uniformly reach them, which fragments the query layer. **Version coupling.** Because these features moved fast across Hibernate 5 → 6 → 6.x, HQL-heavy queries make major upgrades noisier. The Hibernate 5-to-6 migration is a well-known example, where stricter parsing rejected previously tolerated syntax. **Reviewer load.** A window function or recursive CTE inside a query string is powerful and dense. It needs a test and usually a comment, in a way `where b.price > :min` does not. ## A sensible policy Use plain JPQL by default because it is the smaller, better-known language. Reach for HQL when it replaces a native query — window functions, CTEs, set operations and derived roots frequently do, and staying in HQL preserves entity mapping, dialect rendering and cache integration that a native query throws away. Treat HQL-only syntax as an explicit decision, not an accident, and keep those queries covered by integration tests against the real database.

  • Why might using an HQL window function still be preferable to writing a native SQL query?
    HQL keeps you inside the mapped model: entity and attribute names, dialect-specific SQL generated for you, and results mapped through the same machinery, with the query still visible to Hibernate's statistics and caches. A native query hardcodes table and column names, breaks silently when a mapping changes, and gives up automatic dialect rendering.
  • How do you keep HQL-specific syntax from causing surprises during a Hibernate major upgrade?
    Cover those queries with integration tests that run against the real database, prefer named queries so the parser validates them at bootstrap rather than at first call, and keep HQL-only constructs concentrated in a small number of reviewed places instead of scattered across the codebase. Then an upgrade fails loudly in CI rather than quietly in production.

saying these in an interview costs you the question

  • Claiming JPQL and HQL are exactly the same language
  • Believing HQL-only features break database portability rather than provider portability
  • Saying JPQL supports a limit keyword
  • Assuming every HQL construct has a Criteria API equivalent
  • Treating window functions or CTEs as impossible in an ORM and jumping straight to native SQL

context