Criteria API Basics
Build queries programmatically and type-safely with the Hibernate/JPA Criteria API — an alternative to writing HQL as raw strings.
Introduction
HQL is powerful, but it is still just a string — the compiler cannot check it, and a typo in a field name only surfaces at runtime. The Criteria API solves this by letting you build queries as Java objects and method calls instead of text. It is especially useful when a query needs to be assembled dynamically, for example adding filters conditionally based on which search fields a user actually filled in.
- Why the Criteria API exists alongside HQL
- How to build a basic query with CriteriaBuilder and CriteriaQuery
- How to add WHERE-style filtering with predicates
- How to sort results using the Criteria API
Why the Criteria API?
The Criteria API is the JPA-standard, object-oriented way to construct queries. Instead of writing "WHERE s.marks > :minMarks" as text, you build that condition using Java method calls on a CriteriaBuilder. This gives you compile-time checking of structure (though field names are still resolved as strings via the metamodel or plain string literals) and makes it much easier to build queries whose shape changes based on runtime conditions.
| Aspect | HQL | Criteria API |
|---|---|---|
| Form | String query | Java objects and method chains |
| Dynamic queries | Requires manual string building | Naturally composable with if-statements |
| Readability for simple queries | Very readable | More verbose |
| Refactoring safety | Breaks silently on rename | Safer with the static metamodel |
Building a Basic Criteria Query
Every Criteria query starts the same way: get a CriteriaBuilder from the Session, use it to create a CriteriaQuery, define the root entity, and execute it.
Session session = sessionFactory.openSession();
CriteriaBuilder cb = session.getCriteriaBuilder();CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> root = cq.from(Student.class);cq.select(root);
Query<Student> query = session.createQuery(cq);List<Student> students = query.getResultList();
for (Student s : students) { System.out.println(s.getName());}
session.close();Click Run to see what this code prints.
This is the Criteria equivalent of "FROM Student" — cq.select(root) with no filtering fetches every row, translated into the same underlying SQL you saw with HQL.
Filtering with Predicates
Conditions in the Criteria API are represented as Predicate objects, built using methods on CriteriaBuilder such as equal(), greaterThan(), like(), and, and or().
CriteriaBuilder cb = session.getCriteriaBuilder();CriteriaQuery<Student> cq = cb.createQuery(Student.class);Root<Student> root = cq.from(Student.class);
Predicate highMarks = cb.greaterThan(root.get("marks"), 75.0);Predicate nameStartsWithA = cb.like(root.get("name"), "A%");
cq.select(root).where(cb.and(highMarks, nameStartsWithA));
List<Student> results = session.createQuery(cq).getResultList();results.forEach(s -> System.out.println(s.getName() + " - " + s.getMarks()));Click Run to see what this code prints.
Because predicates are just Java objects, you can collect them into a List<Predicate>, add to that list only when a filter value is present, and combine everything at the end with cb.and(predicates.toArray(new Predicate[0])) — exactly the pattern that makes Criteria shine for search screens with optional filters.
Ordering Results
Sorting works the same way — through the CriteriaBuilder, using asc() or desc() on a path expression.
CriteriaBuilder cb = session.getCriteriaBuilder();CriteriaQuery<Student> cq = cb.createQuery(Student.class);Root<Student> root = cq.from(Student.class);
cq.select(root).orderBy(cb.desc(root.get("marks")));
List<Student> rankedStudents = session.createQuery(cq).getResultList();Click Run to see what this code prints.
Common Mistakes
- Reaching for the Criteria API for every simple query — it adds verbosity that plain HQL avoids for straightforward cases.
- Passing raw field name strings like root.get("marks") without validating them, reintroducing the same typo risk Criteria is meant to avoid (the static metamodel solves this but adds build-time generation).
- Forgetting to call .select(root) before executing the query.
- Mixing up cb.and() (all conditions must match) with cb.or() (any condition matches) when combining predicates.
Best Practices
- Reach for the Criteria API specifically when a query's filters are built up dynamically at runtime.
- Keep simple, fixed queries in HQL — it is more readable for anyone maintaining the code later.
- Consider generating the JPA static metamodel (Student_ class) for compile-time-safe field references instead of string literals.
- Build predicate lists incrementally and combine them once at the end, rather than nesting conditional and/or logic.
Frequently Asked Questions
No — both ultimately compile down to the same SQL and go through the same execution engine. The Criteria API's benefit is in how the query is constructed in Java, not in runtime performance.
Not for every query. Use HQL for fixed, readable queries, and reach for Criteria specifically when the query structure needs to change based on runtime conditions, like optional search filters.
It is a set of generated classes (e.g., Student_) with static fields matching your entity's properties, letting you write root.get(Student_.marks) instead of root.get("marks") for compile-time safety.
Key Takeaways
- The Criteria API builds queries as Java objects instead of strings.
- CriteriaBuilder, CriteriaQuery, and Root are the three core building blocks.
- Predicate objects represent WHERE-style conditions and combine with cb.and()/cb.or().
- Criteria queries shine when filters are assembled dynamically at runtime.
Summary
The Criteria API trades some readability for type safety and dynamic composability, making it a valuable tool for building flexible search and filter logic. With single-entity querying covered from both HQL and Criteria angles, it is time to model relationships between entities, starting with the simplest kind: one-to-one.