About
The Criteria API is a type-safe , object-oriented API in JPA used to create dynamic queries programmatically. Instead of writing queries as strings (like JPQL), it lets you build queries using Java objects and methods.
It was introduced in JPA 2.0 to address issues like:
Lack of compile-time checking in JPQL
Difficulty in building dynamic queries using string concatenation
Characteristics of Criteria API
Uses Java types; errors are caught at compile time.
You can build queries conditionally at runtime.
Constructs queries using objects, not string-based syntax.
Good for building queries with complex filters and conditions.
Integrated with EntityManager
Uses EntityManager
to execute queries like JPQL.
Syntax Structure
Here’s how a typical Criteria API query looks:
Copy // 1. Get CriteriaBuilder from EntityManager
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// 2. Create CriteriaQuery
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
// 3. Define root entity
Root<Employee> root = cq.from(Employee.class);
// 4. Add predicates and select
cq.select(root).where(cb.equal(root.get("department"), "IT"));
// 5. Execute query
List<Employee> result = entityManager.createQuery(cq).getResultList();
Step 1: Get CriteriaBuilder
from EntityManager
Copy CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaBuilder
is a factory object that helps create different parts of the query — like conditions (where
), sorting (orderBy
), grouping (groupBy
), etc.
All Criteria queries start with a CriteriaBuilder
. It provides methods like equal()
, greaterThan()
, and()
, etc. — just like we would write in JPQL/SQL, but in an object-oriented way.
Step 2: Create CriteriaQuery
Copy CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Creates the actual query object that will return Employee
objects.
We must tell JPA what type of data we’re querying. This also sets the return type of your query.
Step 3: Define the Root Entity
Copy Root<Employee> root = cq.from(Employee.class);
Defines the main entity/table we're querying from — in SQL terms, this is like saying FROM employee
.
It’s our main data source for the query. The root
is what we use to access the entity fields like root.get("department")
.
Step 4: Add Select and Filter Conditions (Predicates)
Copy cq.select(root).where(cb.equal(root.get("department"), "IT"));
cq.select(root)
: selects the entire Employee entity.
cb.equal(...)
: builds a condition where department = 'IT'
.
.where(...)
: applies that condition to the query.
We are telling JPA
“I want all employees where department is 'IT'.”
We can also chain multiple conditions here using cb.and()
or cb.or()
.
Step 5: Execute the Query
Copy List<Employee> result = entityManager.createQuery(cq).getResultList();
Converts the CriteriaQuery to an executable JPA query.
Runs it and fetches the result as a list of Employee
objects.
This is where the query is actually run on the database , and you get your data back.
Final Output
After all the steps, result
will hold something like:
Copy [
Employee{id=1, name="Alice", department="IT"},
Employee{id=5, name="Bob", department="IT"}
]
Breakdown (like SQL)
Criteria API Equivalent
SQL Equivalent
cb.createQuery(Employee.class)
cb.equal(root.get("department"), "IT")
Examples
Prerequisites
Copy // Entity Class
import jakarta.persistence.*;
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String department;
private double salary;
// Getters and Setters
}
// Repository Interface
import org.springframework.data.jpa.repository.JpaRepository;
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// Empty - we will use Criteria API in service
}
// Service with Criteria API Logic
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class EmployeeService {
@PersistenceContext
private EntityManager entityManager;
// Our methods
}
1. Basic SELECT
Copy public List<Employee> getAllEmployees() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root);
return entityManager.createQuery(cq).getResultList();
}
Fetch all employees.
2. SELECT with WHERE
Copy public List<Employee> getEmployeesByDepartment(String department) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Predicate deptPredicate = cb.equal(root.get("department"), department);
cq.select(root).where(deptPredicate);
return entityManager.createQuery(cq).getResultList();
}
Get employees in Sales department.
3. Using Multiple Conditions
Copy public List<Employee> getEmployeesFromITWithHighSalary() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Predicate p1 = cb.equal(root.get("department"), "IT");
Predicate p2 = cb.greaterThan(root.get("salary"), 50000);
cq.select(root).where(cb.and(p1, p2));
return entityManager.createQuery(cq).getResultList();
}
IT employees with salary > 50,000.
4. Ordering
Copy public List<Employee> getAllEmployeesSortedBySalaryDesc() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
cq.select(root);
cq.orderBy(cb.desc(root.get("salary")));
return entityManager.createQuery(cq).getResultList();
}
Order employees by salary descending.
5. Selecting Specific Fields (Projection)
Copy public List<String> getAllEmployeeNames() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> nameQuery = cb.createQuery(String.class);
Root<Employee> empRoot = nameQuery.from(Employee.class);
nameQuery.select(empRoot.get("name"));
return entityManager.createQuery(nameQuery).getResultList();
}
Fetch employee names.
6. Joins
Copy public List<Employee> getEmployeesFromFinanceDepartment() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Department> deptJoin = root.join("department");
cq.select(root).where(cb.equal(deptJoin.get("name"), "Finance"));
return entityManager.createQuery(cq).getResultList();
}
Get employees in the Finance department.
7. Aggregate Functions
Copy public Double getAverageSalary() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Double> avgSalaryQuery = cb.createQuery(Double.class);
Root<Employee> root = avgSalaryQuery.from(Employee.class);
avgSalaryQuery.select(cb.avg(root.get("salary")));
return entityManager.createQuery(avgSalaryQuery).getSingleResult();
}
Get average salary.
8. Group By and Having
Copy public List<Object[]> getDepartmentsWithMoreThan5Employees() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> groupQuery = cb.createQuery(Object[].class);
Root<Employee> root = groupQuery.from(Employee.class);
groupQuery.multiselect(root.get("department"), cb.count(root))
.groupBy(root.get("department"))
.having(cb.gt(cb.count(root), 5));
return entityManager.createQuery(groupQuery).getResultList();
}
Departments with more than 5 employees.
9. Using IN Clause
Copy public List<Employee> getEmployeesInDepartments() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Predicate inClause = root.get("department").in("IT", "HR", "Finance");
cq.select(root).where(inClause);
return entityManager.createQuery(cq).getResultList();
}
Employees in IT, HR, or Finance.
10. Subqueries
Copy public List<Employee> getEmployeesWithSalaryGreaterThanAvg() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
// Subquery to calculate the average salary
Subquery<Double> sub = cq.subquery(Double.class);
Root<Employee> subRoot = sub.from(Employee.class);
sub.select(cb.avg(subRoot.get("salary")));
// Main query selects employees where their salary is greater than the average
cq.select(root).where(cb.greaterThan(root.get("salary"), sub));
return entityManager.createQuery(cq).getResultList();
}
Employees earning more than average salary.
Criteria API vs JPQL vs SQL
Feature
Criteria API
JPQL
SQL