Introduction
When working with databases in Java applications, queries often need to change based on user input or application requirements. For example, a search screen may allow users to filter records by name, age, status, or multiple conditions at the same time.
JPQL, HQL, and native SQL can handle these requirements, but dynamically building string-based queries can become difficult to maintain.
JPA provides the Criteria API, which allows developers to construct queries programmatically using Java objects and expressions. Hibernate supports the JPA Criteria API and can translate the resulting query into SQL for the underlying database.
In this article, we will learn how to use Criteria API with JPA/Hibernate to retrieve records, add conditions, combine multiple filters, and build dynamic queries.
What Is a Criteria Query?
A Criteria Query is a programmatic way to construct database queries using the JPA Criteria API.
Instead of writing a query as a string such as:
SELECT u FROM User u WHERE u.age > 25
you construct the query using Java objects such as:
CriteriaBuilderCriteriaQueryRootPredicate
This approach is particularly useful when query conditions are determined at runtime.
Why Use the Criteria API?
The Criteria API is useful when an application needs flexible query construction.
Type-Safe Query Construction
The Criteria API represents query components as Java objects rather than a single query string.
For even stronger type safety, you can use the JPA static metamodel instead of string-based property names.
Dynamic Queries
Conditions can be added only when they are required.
For example, a search page might allow users to provide:
Name
Minimum age
Maximum age
Account status
The application can construct the query based on whichever values were provided.
Object-Oriented Approach
Criteria queries work with entity classes rather than directly working with database tables.
Suitable for Complex Filtering
The API supports conditions, joins, ordering, grouping, aggregation, subqueries, and other query operations.
Setting Up the Example
For this example, assume a Java application using:
Java
JPA
Hibernate
An
EntityManagerA relational database
The exact dependency configuration depends on whether you are using Spring Boot, Jakarta Persistence directly, or another Java framework.
Creating the User Entity
Let's create a simple User entity.
For modern Jakarta Persistence applications, the entity can be written as follows:
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
private Long id;
private String name;
private int age;
public User() {
}
public User(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
The User entity represents records in the database.
For older JPA versions, the imports may use javax.persistence instead of jakarta.persistence.
Fetching All Users
The first example retrieves all users.
Step 1: Get the CriteriaBuilder
The CriteriaBuilder is used to create expressions, predicates, and criteria queries.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
Step 2: Create a CriteriaQuery
Create a query that will return User objects.
CriteriaQuery<User> cq = cb.createQuery(User.class);
Step 3: Define the Query Root
The Root represents the entity from which the query starts.
Root<User> root = cq.from(User.class);
Step 4: Select the Root
Select the users represented by the root.
cq.select(root);
Step 5: Execute the Query
List<User> users =
entityManager.createQuery(cq).getResultList();
The complete example is:
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import java.util.List;
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.select(root);
List<User> users =
entityManager.createQuery(cq).getResultList();
Conceptually, the generated SQL is similar to:
SELECT *
FROM User;
The exact SQL generated by Hibernate depends on the entity mapping and database dialect.
Adding a WHERE Condition
Suppose we want users whose age is greater than 25.
With the Criteria API, use CriteriaBuilder to create the condition.
cq.select(root)
.where(cb.gt(root.get("age"), 25));
The important part is:
cb.gt(root.get("age"), 25)
Here:
root.get("age")refers to theageproperty.cb.gt()creates a greater-than condition.25is the comparison value.
The query is conceptually equivalent to:
SELECT *
FROM User
WHERE age > 25;
Adding Multiple Conditions
Real applications commonly need more than one filter.
For example, suppose we want users who:
Are older than 25
Have names beginning with
B
We can combine conditions using cb.and().
Predicate ageCondition =
cb.gt(root.get("age"), 25);
Predicate nameCondition =
cb.like(root.get("name"), "B%");
cq.select(root)
.where(cb.and(ageCondition, nameCondition));
The query is conceptually equivalent to:
SELECT *
FROM User
WHERE age > 25
AND name LIKE 'B%';
Using OR Conditions
The Criteria API also supports OR conditions.
For example:
Predicate ageCondition =
cb.gt(root.get("age"), 40);
Predicate nameCondition =
cb.like(root.get("name"), "A%");
cq.select(root)
.where(cb.or(ageCondition, nameCondition));
This represents:
SELECT *
FROM User
WHERE age > 40
OR name LIKE 'A%';
Adding Sorting
Criteria queries can also specify ordering.
Suppose we want users ordered by age in ascending order:
cq.select(root)
.orderBy(cb.asc(root.get("age")));
For descending order:
cq.select(root)
.orderBy(cb.desc(root.get("age")));
You can also sort by multiple properties:
cq.orderBy(
cb.asc(root.get("name")),
cb.desc(root.get("age"))
);
Building a Dynamic Query
One of the strongest use cases for Criteria API is dynamic filtering.
Suppose a search screen allows the user to provide an optional name and minimum age.
We don't want to add a condition when the corresponding value was not provided.
Join the conversation! Your thoughts help the community grow.