skip to content

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