What is Criteria API and When to Use It
Criteria API is a type-safe way of building JPQL queries programmatically. It's especially useful for dynamic queries with many optional filters, where static JPQL would be inco...
Overview
Criteria API is a type-safe way of building JPQL queries programmatically. It’s especially useful for dynamic queries with many optional filters, where static JPQL would be inconvenient.
Junior Level
What is Criteria API
Criteria API - type-safe way to build JPQL queries programmatically.
// JPQL - string query
"SELECT u FROM User u WHERE u.age > 18"
// Criteria API - type-safe
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.select(root).where(cb.gt(root.get("age"), 18));
List<User> users = entityManager.createQuery(query).getResultList();
Main Components
CriteriaBuilder - factory for creating expressions
CriteriaQuery<T> - the query itself
Root<T> - root entity (FROM clause)
Predicate - WHERE condition
Simple Example
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
// SELECT u FROM User u WHERE u.age > 18
query.select(root)
.where(cb.greaterThan(root.get("age"), 18));
// String-based access - NOT type-safe. For type-safety use Metamodel:
// root.get(User_.age)
List<User> users = entityManager.createQuery(query).getResultList();
Middle Level
When to Use
Criteria API when:
- Dynamic queries (many optional filters)
- Type-safety needed (compilation catches errors)
- Safe refactoring
JPQL when:
- Simple static queries
- Easier to read
- Less code
Dynamic Query with Filters
public List<User> findUsers(String name, Integer minAge, String status) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (name != null) {
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
if (minAge != null) {
predicates.add(cb.ge(root.get("age"), minAge));
}
if (status != null) {
predicates.add(cb.equal(root.get("status"), status));
}
query.where(predicates.toArray(new Predicate[0]));
query.orderBy(cb.asc(root.get("name")));
return entityManager.createQuery(query).getResultList();
}
With Metamodel (Type-Safe)
// Metamodel generated automatically
@StaticMetamodel(User.class)
public abstract class User_ {
public static volatile SingularAttribute<User, String> name;
public static volatile SingularAttribute<User, Integer> age;
public static volatile SingularAttribute<User, String> status;
}
// Type-safe query
root.get(User_.age) // instead of root.get("age")
JOIN in Criteria API
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> root = query.from(Order.class);
Join<Order, User> userJoin = root.join("user");
query.select(root)
.where(cb.equal(userJoin.get("name"), "John"));
Senior Level
Specification Pattern (Spring Data JPA)
public class UserSpecifications {
public static Specification<User> hasName(String name) {
return (root, query, cb) ->
cb.like(root.get(User_.name), "%" + name + "%");
}
public static Specification<User> hasMinAge(int minAge) {
return (root, query, cb) ->
cb.greaterThanOrEqualTo(root.get(User_.age), minAge);
}
public static Specification<User> hasStatus(String status) {
return (root, query, cb) ->
cb.equal(root.get(User_.status), status);
}
}
// Usage
Specification<User> spec = Specification
.where(UserSpecifications.hasName("John"))
.and(UserSpecifications.hasMinAge(18))
.and(UserSpecifications.hasStatus("ACTIVE"));
List<User> users = userRepository.findAll(spec);
Subqueries
// Simplified example - in real code subqueries can be much more complex
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> userRoot = query.from(User.class);
// Subquery
Subquery<Long> subquery = query.subquery(Long.class);
Root<Order> orderRoot = subquery.from(Order.class);
subquery.select(cb.max(orderRoot.get("total")))
.where(cb.equal(orderRoot.get("user").get("id"), userRoot.get("id")));
query.select(userRoot)
.where(cb.greaterThan(userRoot.get("totalSpent"), subquery));
Fetch Joins
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> root = query.from(Order.class);
root.fetch("items", JoinType.LEFT); // FETCH JOIN
query.select(root)
.distinct(true); // eliminate duplicates
Metamodel Generation
<!-- pom.xml -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<scope>provided</scope>
</dependency>
// Generates User_, Order_, etc. automatically
Best Practices
Criteria API for dynamic queries
Metamodel for type-safety
JPQL for simple static queries
Spring Data JPA Specification
Fetch joins for related data
Distinct for duplicates
Criteria API for simple queries (excessive)
Without metamodel (string-based prone to errors)
String concat in Criteria - query plan cache pollution and unreadability. Values are still parameterized, SQL injection unlikely.
Ignoring distinct on fetch joins
Interview Cheat Sheet
Must know:
- Criteria API - type-safe way of building JPQL queries programmatically
- Main components: CriteriaBuilder, CriteriaQuery, Root, Predicate
- Best for dynamic queries with many optional filters
- Metamodel (User_) provides full type-safety - errors at compile time
- Specification pattern (Spring Data JPA) - reusable criteria
- For simple static queries, JPQL is simpler and more readable
Frequent follow-up questions:
- Criteria API vs JPQL - when which? Criteria API for dynamic queries (optional filters), JPQL for static
- Why Metamodel? root.get(“age”) - string-based, runtime errors; root.get(User_.age) - type-safe, compile-time errors
- What does Specification pattern give? Criteria composition: where(hasName).and(hasMinAge), code reuse
- Does Criteria API support subqueries and fetch joins? Yes - Subquery for subqueries, root.fetch() for fetch joins
Red flags (DO NOT say):
- “Criteria API for simple SELECT” - excessive, JPQL simpler
- “Without Metamodel - I use strings” - runtime errors on refactoring
- “String concat in Criteria” - query plan cache pollution, unreadability
- “I ignore distinct on fetch joins” - duplicates in results
Related topics:
- [[26. What is JPQL and How Does It Differ from SQL]]
- [[28. How to Use JOIN FETCH to Solve the N+1 Problem]]
- [[29. What is Projection in JPA]]
- [[23. How to Properly Use @OneToMany and @ManyToOne]]