skip to content

HQL, JPQL & Criteria API

Hibernate's query toolbox: entity-oriented JPQL/HQL, type-safe Criteria for dynamic filters, native SQL escape hatches, DTO projections, pagination, and bulk statements. Interviewers probe here to see whether you can pick the right query tool and know which ones bypass the persistence context.

part ofHibernateoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

Walk through how you build and execute a simple query with the JPA Criteria API — what roles do CriteriaBuilder, CriteriaQuery, Root and TypedQuery each play?

level: juniorimportance: must knowfreq 55%

answer

  1. cb → cq → root → select/where → em.createQuery
  2. Root = FROM entry + path navigator
  3. where() replaces, doesn't append
  4. values become bind parameters, not literals
  5. same translator as JPQL, same SQL

basics

~20 s

EntityManager.getCriteriaBuilder() gives a CriteriaBuilder, the factory for everything. It creates a CriteriaQuery<T> that fixes the result type. Root<E> is the FROM entity and the handle for attribute paths. You set select/where/orderBy, then em.createQuery(cq) returns an executable TypedQuery<T>.

solid answer

~50 s

The Criteria API builds a query as an object tree instead of a string. 1. `CriteriaBuilder cb = em.getCriteriaBuilder()` — the factory for queries, predicates, expressions and functions. 2. `CriteriaQuery<Book> cq = cb.createQuery(Book.class)` — declares the **result type** of the query. 3. `Root<Book> book = cq.from(Book.class)` — adds a FROM clause entry; the `Root` is also the navigation handle: `book.get("title")` is a `Path` you feed into predicates. 4. Compose the clauses: `cq.select(book).where(cb.equal(book.get("status"), Status.ACTIVE)).orderBy(cb.asc(book.get("title")))`. 5. `TypedQuery<Book> q = em.createQuery(cq)` — turns the tree into an executable query; from here it behaves exactly like a JPQL `TypedQuery`: `setParameter`, `setFirstResult/setMaxResults`, `getResultList`, `getSingleResult`. The result type of `createQuery` need not be the entity — `cb.createQuery(String.class)` with `cq.select(book.get("title"))` returns titles, and `cb.createTupleQuery()` returns multi-column `Tuple` rows. Hibernate translates that tree into the same SQL a JPQL query would produce; there is no separate execution engine.

code

java · 11 lines
java
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> book = cq.from(Book.class);

cq.select(book)
  .where(cb.equal(book.get("status"), Status.ACTIVE))
  .orderBy(cb.asc(book.get("title")));

List<Book> result = em.createQuery(cq)
        .setMaxResults(50)
        .getResultList();

go deeper

for a junior

Name the four objects and their order — builder, query, root, TypedQuery — and show a where/orderBy on a single entity.

for a middle

Add the non-entity result types (single path, Tuple, cb.construct) and the where()-replaces gotcha, and explain that criteria and JPQL share the same translator.

for a senior

Talk about parameters vs inline values for reusable query definitions, root ownership per query, and when programmatic building is worth the verbosity.

for a principal

Frame it as a query-construction strategy: which layer owns predicate composition, how teams keep criteria code readable, and the maintenance cost of hand-built query trees versus declarative strings.

## What the Criteria API is JPQL is a query *language*: you write a string, and the provider parses it at runtime. The Criteria API is the same query model expressed as **Java objects you assemble programmatically**. Instead of `"select b from Book b where b.status = :s"`, you build a small tree of nodes: a query node, a from node, a comparison node. Hibernate then renders that tree to SQL exactly as it renders a parsed JPQL string — same translator, same SQL, same performance characteristics. The API lives in `jakarta.persistence.criteria` (`javax.persistence.criteria` before Jakarta EE 9). ## The four objects **CriteriaBuilder** — obtained from `EntityManager.getCriteriaBuilder()` (or the `EntityManagerFactory`). It is the factory for *everything*: `createQuery`, `equal`, `like`, `and`, `or`, `greaterThan`, `count`, `sum`, `upper`, `asc`, `desc`, `construct`, subqueries. If you need an operator, look for it on the builder first. **CriteriaQuery<T>** — the query itself, with `T` fixed as the result type. It holds the clauses: `select`, `multiselect`, `where`, `groupBy`, `having`, `orderBy`, `distinct`. Setter-style methods return the query so they chain. A `CriteriaQuery` is a *definition*, not an execution: you can build it once and execute it many times, but you cannot mix roots between two different `CriteriaQuery` instances — a `Root` belongs to the query that created it, which is why a paginated list query and its matching count query each need their own root. **Root<E>** — created by `cq.from(Entity.class)`, it represents one entry in the FROM clause (a query can have several roots, producing a cross join constrained by the WHERE clause). It is the entry point for **paths**: `root.get("author").get("name")` navigates a to-one association and renders as an implicit join. Paths are typed as `Path<Y>`/`Expression<Y>`; when the compiler cannot infer the type from a string key you sometimes need `root.<String>get("title")` or the static metamodel. **TypedQuery<T>** — `em.createQuery(criteriaQuery)` hands the tree to the provider and returns the ordinary JPA execution handle. Everything you know about executing JPQL applies: `getResultList()`, `getSingleResult()`/`getResultStream()`, `setMaxResults`, `setLockMode`, hints. ## Selecting something other than the entity - Single column: `cb.createQuery(String.class)` + `cq.select(root.get("title"))`. - Several columns as a tuple: `cb.createTupleQuery()` + `cq.multiselect(root.get("id"), root.get("title"))`, then read `tuple.get(0, Long.class)`. - Aggregate: `cb.createQuery(Long.class)` + `cq.select(cb.count(root))`. - A DTO: `cq.select(cb.construct(BookView.class, root.get("id"), root.get("title")))` — the object-tree equivalent of a constructor expression. If you call `cq.from(...)` and never call `select`, and the result type matches the single root's type, providers default the selection to that root — but write `select` explicitly; it costs nothing and removes ambiguity. ## Parameters and literals Two styles. You can pass a plain Java value: `cb.equal(root.get("status"), Status.ACTIVE)` — Hibernate turns it into a **JDBC bind parameter**, not an inlined literal, so it is safe from injection and friendly to statement caching. Or you can declare an explicit `ParameterExpression<String> p = cb.parameter(String.class, "title")`, use it in the predicate, and bind at execution with `query.setParameter("title", value)`. Use explicit parameters when you want to build the `CriteriaQuery` once and execute it repeatedly with different values; use inline values for the common build-per-request case. ## Why it exists The payoff is not aesthetics — Criteria code is more verbose than JPQL. It is that the query is **data you can manipulate**: add a predicate inside an `if`, hand a `List<Predicate>` around, generate ORDER BY from a sort field, refactor an attribute name and let the compiler find the usages (with the static metamodel). For a query whose shape is fixed, string JPQL is shorter and easier to read, and most teams keep those in JPQL or named queries. ## Common mistakes Treating clause calls as accumulative: a second `cq.where(...)` **replaces** the first, so combine predicates yourself with `cb.and(...)`. Reusing a `Root` from another `CriteriaQuery`. Expecting `Criteria` (the old `org.hibernate.Criteria` session API) — that is a different, removed API; the JPA Criteria API is the survivor.

  • Can you reuse the same Root object in a second CriteriaQuery, for example to build a matching count query for a paginated list?
    No. A Root belongs to the CriteriaQuery that created it, and mixing them yields an invalid tree or a provider error. For a count query you create a second CriteriaQuery<Long>, call from() again to get a fresh root, and rebuild the predicates against that root. The usual pattern is a helper method that takes a Root and a CriteriaBuilder and returns the List<Predicate>, so the same filtering logic feeds both queries.
  • When you write cb.equal(root.get("status"), Status.ACTIVE) with a literal Java value, does Hibernate inline that value into the SQL?
    No — by default it renders a JDBC bind parameter and binds the value, so the SQL text stays constant and is safe from injection and friendly to the statement cache. Explicit ParameterExpression objects exist for a different reason: they let you build the CriteriaQuery once and execute it many times with different values via setParameter. Hibernate does expose settings that change literal handling, but the default is binding.
  • Calling cq.where(a) and then cq.where(b) — what does the query end up filtering on?
    Only b. The clause setters replace rather than accumulate, which is a classic source of silently wrong results. To combine you build the predicates yourself and pass them together: cq.where(cb.and(a, b)), or cq.where(predicateArray), which the spec treats as conjunction.

JPQL is writing a sentence; the Criteria API is assembling that same sentence from labelled Lego blocks — clumsier to read, but you can hand pieces around and snap on another clause at runtime.

saying these in an interview costs you the question

  • Thinking the Criteria API produces different or slower SQL than the equivalent JPQL string
  • Believing repeated where() calls accumulate as AND
  • Confusing the JPA Criteria API with the removed legacy org.hibernate.Criteria session API
  • Assuming literal values passed to cb.equal are string-concatenated into SQL (an injection risk)
  • Claiming a CriteriaQuery is executable on its own, without em.createQuery

context

open as a page

How do you make a JPQL query return instances of a plain DTO class instead of mapped entities using a constructor expression, and what must the DTO class provide for it to work?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Write select new com.example.OrderView(o.id, o.total, o.customer.name) from Order o. The class name must be fully qualified, and the class needs a public constructor whose parameter types and order match the selected expressions. Results come back as plain, unmanaged objects.

open as a page

In a JPQL query, how do you write an inner join and a left outer join between two entities, and how do the results differ?

level: juniorimportance: must knowfreq 72%

basics

~20 s

You join an association path, not a table: SELECT a FROM Author a JOIN a.books b. Inner join drops authors with no books; LEFT JOIN keeps them with b as null. The mapping supplies the join columns, so no ON clause is required.

open as a page

What is JPQL, and how does a JPQL query differ from the SQL statement that eventually runs against the database?

level: juniorimportance: must knowfreq 78%

basics

~20 s

JPQL is a query language over the entity model: you name entity classes and their Java field names, not tables and columns. The persistence provider translates it into vendor-specific SQL using the mappings, and returns managed entities rather than rows.

open as a page

In JPA, what is a named query, how do you declare one with the @NamedQuery annotation, and how do you execute it through the EntityManager?

level: juniorimportance: must knowfreq 58%

basics

~20 s

A named query is a JPQL string declared once under a name, usually with @NamedQuery on an entity class. You execute it with entityManager.createNamedQuery("Name", Result.class), bind parameters, then getResultList() or getSingleResult(). The name is global to the persistence unit.

open as a page

Using a plain JPA EntityManager, how do you execute raw SQL, and what shape are the results depending on which createNativeQuery overload you call?

level: juniorimportance: must knowfreq 58%

basics

~20 s

entityManager.createNativeQuery(sql) returns untyped rows — each row is an Object[] of column values (or a single Object for one column). createNativeQuery(sql, Book.class) maps rows to managed entities. createNativeQuery(sql, "MappingName") uses a declared @SqlResultSetMapping for richer shapes.

open as a page

In plain JPA/Hibernate (no framework helpers), how do you return only one page of rows from a JPQL query, and what SQL does Hibernate actually send to the database?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Call setFirstResult(offset) and setMaxResults(pageSize) on the TypedQuery. Hibernate appends the dialect's limit clause (LIMIT ... OFFSET ..., or OFFSET ... FETCH FIRST), so the database returns only that page. Always pair it with a deterministic ORDER BY.

open as a page

You run the JPQL statement UPDATE Product p SET p.price = p.price * 1.1 through Query.executeUpdate(). What happens to entities already loaded in the current persistence context, and what happens to Hibernate's second-level cache?

level: middleimportance: must knowfreq 60%

basics

~20 s

The statement becomes one SQL UPDATE run directly on the database. Managed entities already in the persistence context are not refreshed and keep stale values; Hibernate does evict the affected regions of the second-level cache. Clear or refresh afterwards.

open as a page

A search endpoint accepts five optional filter fields, any combination of which may be present. How would you build that query with the JPA Criteria API, and why is that preferable to concatenating a JPQL string?

level: middleimportance: must knowfreq 60%

basics

~20 s

Create one CriteriaQuery and Root, then collect a List<Predicate>: for each filter that is non-null, add the matching predicate. Finish with cq.where(list.toArray(new Predicate[0])), which ANDs them. No string building, no dangling AND, values bound as parameters.

open as a page

For a read-only list screen, why would you project query results into DTOs rather than loading the mapped entities and reading their fields?

level: middleimportance: must knowfreq 50%

basics

~20 s

Loading entities selects every mapped column, puts each object in the persistence context, and stores a snapshot copy for dirty checking that Hibernate must compare at flush. A DTO query selects only the needed columns, allocates one small object per row, and is never dirty-checked or flushed.

open as a page

When a JPQL query filters on a nested path such as 'where o.customer.address.city = :city' on an Order entity, what SQL does the provider generate, and why should you care?

level: middleimportance: must knowfreq 60%

basics

~20 s

Each dot across a to-one association becomes a join in the SQL: two hops means two joins you never wrote. They are inner joins by default, they can silently multiply if repeated, and they are invisible in the JPQL text.

open as a page

What ways does JPA give you to bind values into a JPQL query, and why should a value never be concatenated into the query string?

level: middleimportance: must knowfreq 68%

basics

~10 s

Named parameters (:name) and positional ones (?1), both bound with setParameter. Concatenating values instead allows injection through the entity model, defeats JDBC prepared-statement reuse, and creates a new query plan per distinct value.

open as a page

Teams sometimes move JPQL out of inline createQuery calls into @NamedQuery declarations specifically for the fail-fast behaviour. What exactly does JPA/Hibernate check when the persistence unit boots, what does it not check, and how does that differ for @NamedNativeQuery?

level: middleimportance: must knowfreq 46%

basics

~20 s

At bootstrap Hibernate parses every named JPQL query and resolves entity and attribute names against the mappings, so typos and stale field references fail startup. It does not run the query or check the real database schema. Named native SQL is registered unparsed and only fails when executed.

open as a page

A raw SQL query returns one row per order joined with the customer row and a computed total. In JPA, how do you declare a @SqlResultSetMapping so that each row arrives as two entities plus a scalar, or as a DTO instead of an Object[]?

level: middleimportance: must knowfreq 44%

basics

~20 s

Declare a @SqlResultSetMapping naming the pieces of each row: @EntityResult (with @FieldResult when column aliases differ from the mapped names) for entities, @ColumnResult for scalars, and @ConstructorResult to build a DTO from listed columns. Pass its name to createNativeQuery(sql, "MappingName").

open as a page

A JPQL query left-join-fetches a one-to-many collection and also calls setMaxResults; Hibernate logs a warning that firstResult/maxResults were specified with a collection fetch and are being applied in memory. What is Hibernate actually doing, and why is that dangerous in production?

level: middleimportance: must knowfreq 55%

basics

~20 s

A collection fetch join returns one SQL row per child, so a SQL LIMIT would cut a parent's collection in half. Hibernate drops the limit, runs the whole query, builds every matching parent in memory, then slices — unbounded reads and heap use.

open as a page

Does a JPQL statement DELETE FROM Order o WHERE o.createdAt < :cutoff, executed with executeUpdate(), also remove the child rows mapped with cascade = REMOVE or orphanRemoval? What typically goes wrong if you assume it does?

level: middleimportance: should knowfreq 40%

basics

~20 s

No. A bulk DELETE emits SQL against the entity's own table only; cascade and orphanRemoval are object-layer features that are skipped. Children remain, and foreign keys usually make the statement fail with a constraint violation.

open as a page

What is the JPA static metamodel — generated classes such as Customer_ with fields like Customer_.name — how is it produced, and what does it give you over passing attribute names as plain strings?

level: middleimportance: should knowfreq 35%

basics

~20 s

An annotation processor generates a companion class per entity (Customer_) holding typed attribute descriptors. Using root.get(Customer_.name) instead of root.get("name") gives compile-time checking of the attribute name and its Java type, so renames and type mistakes break the build instead of failing at runtime.

open as a page

Apart from JPQL constructor expressions, what other ways can a JPQL/HQL query return a non-entity shape — for example jakarta.persistence.Tuple or a Java record — and when would you choose each?

level: middleimportance: should knowfreq 32%

basics

~20 s

Object[] rows for a raw multi-column select; jakarta.persistence.Tuple for the same rows with named/typed accessors via aliases; and in Hibernate 6, passing a record or class to createQuery lets the provider instantiate it from a plain select list, without new. Constructor expressions remain the portable choice.

open as a page

In a JPQL query with a LEFT JOIN, what is the difference between putting an extra restriction in the JOIN ... ON clause and putting it in the WHERE clause?

level: middleimportance: should knowfreq 48%

basics

~20 s

ON restricts which rows may join, so unmatched parents survive with nulls. WHERE filters the joined result, so a condition on the outer side removes those parents and turns the outer join into an inner one. For inner joins the two are equivalent.

open as a page

Which functions can you rely on in a portable JPQL query, and how do you call a database-specific function that JPQL does not define?

level: middleimportance: should knowfreq 44%

basics

~10 s

JPQL defines a small set: string (concat, substring, trim, lower, upper, length, locate), arithmetic (abs, mod, sqrt, size, index), datetime (current_date/time/timestamp), plus coalesce, nullif and CASE. Anything else goes through FUNCTION('name', args).

open as a page

How does TypedQuery differ from the untyped Query interface in JPA, and what happens when getSingleResult() matches zero rows or more than one?

level: middleimportance: should knowfreq 52%

basics

~20 s

TypedQuery<T> carries the result type you pass to createQuery, so getResultList returns List<T> without a cast. getSingleResult throws NoResultException for zero rows and NonUniqueResultException for more than one; use getResultList or getSingleResultOrNull when absence is normal.

open as a page

JPA lets you declare named queries either with annotations on entity classes or in an XML mapping file such as META-INF/orm.xml. What does the XML route give you, and what are the override rules when the same query name appears in both places?

level: middleimportance: should knowfreq 34%

basics

~20 s

orm.xml declares the same named queries outside Java, so query text can be reviewed, edited or swapped per deployment without recompiling entities. XML metadata takes precedence over annotations: a named query defined in orm.xml with the same name replaces the annotated one.

open as a page

For a paged JPQL query you also need the total number of matching rows. How do you write that count query, and what goes wrong if you reuse the paged query's text with a count added?

level: middleimportance: should knowfreq 38%

basics

~20 s

Write a separate JPQL count: same FROM and WHERE, no ORDER BY, no join fetch (Hibernate rejects a fetch whose owner is not selected). If a collection join is needed for filtering, use count(distinct e.id), or the join multiplies the count.

open as a page

When would you reach for the JPA Criteria API instead of a string JPQL query, and when is Criteria the wrong tool? Be concrete about the costs of each.

level: seniorimportance: should knowfreq 45%

basics

~20 s

Use Criteria when the query shape varies at runtime — optional filters, dynamic sorting, composable predicates — or when compile-time attribute safety matters. Use string JPQL for fixed-shape queries: far more readable, reviewable, pasteable. Same translator, same SQL, so it is a maintainability call, not performance.

open as a page

You need one row per order carrying the customer's name and the number of line items on that order. How do you shape that as a JPQL DTO projection, and what goes wrong when a projection spans a to-many association?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Join the to-one customer flatly and aggregate the to-many: select new View(o.id, c.name, count(l)) from Order o join o.customer c left join o.lines l group by o.id, c.name. Without the aggregate, joining a to-many multiplies rows — one per line item — so the DTO is instantiated repeatedly per order.

open as a page

What happens to the result of a JPQL query that joins two different collection associations of the same entity in one statement, and how do you avoid the row explosion?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The two collections multiply: a parent with 5 of one and 4 of the other yields 20 rows. Results and any aggregates are inflated. Split into separate queries, use EXISTS subqueries for filtering, or aggregate per collection separately.

open as a page

An EntityManager has unsaved changes pending when raw SQL is executed through createNativeQuery. What does Hibernate do about the pending state, why does it behave differently than for a JPQL query, and how do you narrow that behaviour?

level: seniorimportance: should knowfreq 32%

basics

~20 s

For JPQL, Hibernate flushes only if pending changes touch tables the query reads. It cannot parse native SQL, so it assumes the query touches everything: it flushes the whole session and invalidates all cached query results. Declaring the query's synchronized tables or entity classes narrows both.

open as a page

Offset-based paging gets slower and less stable the deeper a user scrolls through a large sorted result set. How would you express keyset (seek) pagination in JPQL instead, and what does it give up?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Instead of an offset, carry the last row's sort key and ask for rows after it: 'where (e.createdAt, e.id) < (:lastCreatedAt, :lastId) order by e.createdAt desc, e.id desc' with setMaxResults. Constant cost and stable pages, but no jumping to page N.

open as a page

You need a correctly sized page of parent entities with their child collections already initialised, without Hibernate paginating in memory. Describe the two-query approach and what each query looks like.

level: seniorimportance: should knowfreq 42%

basics

~20 s

Query one selects only ids with setFirstResult/setMaxResults, so the limit reaches SQL. Query two fetch-joins the collections with 'where p.id in :ids' and no limit — bounded by the id list. Re-apply the ORDER BY, because IN does not preserve order.

open as a page

You must apply a price change to roughly ten million rows in a live system that uses Hibernate with a second-level cache and concurrent users. How do you decide between one JPQL bulk UPDATE, chunked bulk statements, and loading entities in batches?

level: principalimportance: should knowfreq 28%

basics

~20 s

Decide on three axes: is the change expressible in SQL, how long may one transaction hold locks, and who else holds those rows. Usually chunked bulk statements committed per chunk - set-based speed without one huge transaction - plus deliberate cache invalidation and version bumping.

open as a page

showing 1–30 of 38