Criteria API

This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Three older reference books were consulted as bibliography only while preparing these pages and are not the primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

This section’s bibliography lists the reference material consulted while preparing these pages.

Where HQL/JPQL queries are strings, the Criteria API builds a query as a tree of typed Java objects — verbose, but type-checked at compile time and naturally suited to a query whose shape depends on runtime conditions.

CriteriaBuilder, CriteriaQuery, Root

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> root = cq.from(Book.class);

cq.select(root)
  .where(cb.equal(root.get("title"), "Effective Java"));

List<Book> results = em.createQuery(cq).getResultList();

CriteriaBuilder is the factory for predicates, expressions, and the query object itself; CriteriaQuery<T> is the query being built; Root<T> is the query’s FROM entity, the starting point for navigating attributes and joins.

The static metamodel

root.get("title") above is a string, losing the compile-time safety the Criteria API is otherwise known for. The JPA static metamodel — generated at compile time by an annotation processor (hibernate-jpamodelgen) from each @Entity — provides typed attribute references instead:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-jpamodelgen</artifactId>
    <scope>provided</scope>
</dependency>
// generated: Book_.title, Book_.author (in package com.example, class Book_)
cq.where(cb.equal(root.get(Book_.title), "Effective Java"));

The generated Book_ class mirrors Book’s attributes as typed `SingularAttribute/ListAttribute/…​ fields, so root.get(Book_.title) is checked against Book’s actual `title type at compile time, and a renamed/removed field breaks the build immediately instead of failing at runtime with a string typo.

Joins and fetch joins

Root<Book> root = cq.from(Book.class);
Join<Book, Author> authorJoin = root.join(Book_.author); // INNER JOIN by default
authorJoin = root.join(Book_.author, JoinType.LEFT);

cq.select(root).where(cb.equal(authorJoin.get(Author_.name), "King"));

// fetch join -- see fetching-and-n-plus-1.adoc
root.fetch(Book_.author, JoinType.LEFT);

join() and fetch() are separate calls that can both target the same association — fetch() controls whether the association is eagerly loaded in this query’s result, join() controls whether it can be referenced in WHERE/ORDER BY; typically only one of the two is needed per association per query.

Parameters and predicates

ParameterExpression<String> namePar = cb.parameter(String.class, "name");
cq.where(cb.equal(authorJoin.get(Author_.name), namePar));

TypedQuery<Book> query = em.createQuery(cq);
query.setParameter("name", "King");

Predicate combinators: cb.and(…​), cb.or(…​), cb.not(…​), plus the same comparison/LIKE/BETWEEN/ IN predicates JPQL exposes, built as method calls on CriteriaBuilder rather than parsed from a string.

SelectionSpecification/MutationSpecification

Newer JPA/Hibernate APIs for building a query programmatically from an existing one, rather than from scratch — particularly convenient for composing optional filters onto a base query:

SelectionSpecification<Book> spec = SelectionSpecification.create(Book.class)
        .restrict((root, query, cb) -> cb.equal(root.get(Book_.outOfPrint), false));

if (minYear != null) {
    spec = spec.restrict((root, query, cb) -> cb.ge(root.get(Book_.publishedYear), minYear));
}

List<Book> books = spec.createQuery(em).getResultList();

MutationSpecification is the equivalent for bulk UPDATE/DELETE statements (see Bulk Operations & Batching), composed the same way. Hibernate’s own HibernateCriteriaBuilder (obtained via session.getCriteriaBuilder()) extends the standard CriteriaBuilder with HQL-only capabilities not exposed by the portable JPA interface — insert-select criteria queries, additional function support, and criteria-based bulk mutation queries predating MutationSpecification.

Composing a query from runtime conditions

The Criteria API’s main practical advantage over string-based HQL/JPQL is exactly this: building up a WHERE clause whose shape depends on which filters the caller actually supplied, without string concatenation:

public List<Book> search(String titleContains, Integer minYear, String authorName) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Book> cq = cb.createQuery(Book.class);
    Root<Book> root = cq.from(Book.class);

    List<Predicate> predicates = new ArrayList<>();
    if (titleContains != null) {
        predicates.add(cb.like(root.get(Book_.title), "%" + titleContains + "%"));
    }
    if (minYear != null) {
        predicates.add(cb.ge(root.get(Book_.publishedYear), minYear));
    }
    if (authorName != null) {
        predicates.add(cb.equal(root.join(Book_.author).get(Author_.name), authorName));
    }

    cq.select(root).where(predicates.toArray(new Predicate[0]));
    return em.createQuery(cq).getResultList();
}

Compare with Spring Data JPA’s Specifications, which wrap this exact pattern behind JpaSpecificationExecutor so a repository method can accept a composable Specification<T> instead of building CriteriaQuery by hand.