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?
answer
- cb → cq → root → select/where → em.createQuery
- Root = FROM entry + path navigator
- where() replaces, doesn't append
- values become bind parameters, not literals
- same translator as JPQL, same SQL
basics
~20 sEntityManager.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 sThe 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 linesCriteriaBuilder 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
Name the four objects and their order — builder, query, root, TypedQuery — and show a where/orderBy on a single entity.
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.
Talk about parameters vs inline values for reusable query definitions, root ownership per query, and when programmatic building is worth the verbosity.
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