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
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.