Criteria API

Advanced
Querying

The Criteria API builds type-safe dynamic queries programmatically using metamodel classes; more verbose than JPQL but catches column/type errors at compile time.

Overview

The JPA Criteria API allows building queries programmatically using a fluent builder pattern and statically-generated metamodel classes. Unlike JPQL strings — which fail only at runtime — Criteria queries fail at compile time when field names change, making them safer for complex dynamic search scenarios where filters are optional. The metamodel is generated by a JPA annotation processor (hibernate-jpamodelgen) and produces classes like Order_ with typed field descriptors (SingularAttribute<Order, String>) for each entity field. The API is verbose but predictable; for most Spring Boot applications, Spring Data Specifications wrap Criteria API in a cleaner fluent interface.

Static metamodel and basic Criteria query

Configure the Hibernate metamodel generator to produce Order_ classes. These provide compile-time-safe field references for building Criteria queries.

Java — metamodel setup + basic Criteria query
<!-- pom.xml — metamodel generator -->
<dependency>
  <groupId>org.hibernate.orm</groupId>
  <artifactId>hibernate-jpamodelgen</artifactId>
  <version>6.4.0.Final</version>
  <scope>provided</scope>
</dependency>

// Generated: Order_.java (do not edit — auto-generated)
// @StaticMetamodel(Order.class)
// public abstract class Order_ {
//     public static volatile SingularAttribute<Order, Long>   id;
//     public static volatile SingularAttribute<Order, String> status;
//     public static volatile SingularAttribute<Order, BigDecimal> amount;
//     public static volatile ListAttribute<Order, OrderItem>  items;
// }

// Basic Criteria query
public List<Order> findByStatus(String status) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Order> query = cb.createQuery(Order.class);
    Root<Order> root = query.from(Order.class);

    query.select(root)
         .where(cb.equal(root.get(Order_.status), status))
         .orderBy(cb.desc(root.get(Order_.amount)));

    return em.createQuery(query)
             .setMaxResults(100)
             .getResultList();
}

Dynamic multi-filter search

Criteria API shines when query conditions are optional. Build predicates conditionally and combine them with cb.and().

Java — dynamic multi-filter Criteria query
public List<Order> searchOrders(OrderSearchRequest req) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Order> query = cb.createQuery(Order.class);
    Root<Order> root = query.from(Order.class);

    List<Predicate> predicates = new ArrayList<>();

    // Each filter is optional — only add predicates for non-null params
    if (req.getStatus() != null) {
        predicates.add(cb.equal(root.get(Order_.status), req.getStatus()));
    }
    if (req.getMinAmount() != null) {
        predicates.add(cb.ge(root.get(Order_.amount), req.getMinAmount()));
    }
    if (req.getCustomerId() != null) {
        predicates.add(cb.equal(root.get(Order_.customerId), req.getCustomerId()));
    }
    if (req.getFromDate() != null) {
        predicates.add(cb.greaterThanOrEqualTo(
            root.get(Order_.createdAt), req.getFromDate()));
    }
    if (req.getItemSku() != null) {
        // Join to items sublist
        Join<Order, OrderItem> items = root.join(Order_.items, JoinType.INNER);
        predicates.add(cb.equal(items.get(OrderItem_.sku), req.getItemSku()));
    }

    query.where(cb.and(predicates.toArray(new Predicate[0])));
    query.orderBy(cb.desc(root.get(Order_.createdAt)));

    return em.createQuery(query)
             .setFirstResult(req.getPage() * req.getSize())
             .setMaxResults(req.getSize())
             .getResultList();
}

Spring Data Specifications (recommended wrapper)

Spring Data JpaSpecificationExecutor wraps Criteria API with a cleaner interface. Specifications are composable (and, or, not) and reusable across repository methods.

Java — Spring Data Specifications wrapping Criteria API
// Repository
public interface OrderRepository extends JpaRepository<Order, Long>,
                                          JpaSpecificationExecutor<Order> {}

// Reusable Specification components
public class OrderSpecifications {

    public static Specification<Order> hasStatus(String status) {
        return (root, query, cb) ->
            status == null ? null : cb.equal(root.get(Order_.status), status);
    }

    public static Specification<Order> amountAtLeast(BigDecimal min) {
        return (root, query, cb) ->
            min == null ? null : cb.ge(root.get(Order_.amount), min);
    }

    public static Specification<Order> createdAfter(LocalDateTime from) {
        return (root, query, cb) ->
            from == null ? null : cb.greaterThanOrEqualTo(root.get(Order_.createdAt), from);
    }
}

// Usage: compose specs with and()
Specification<Order> spec = Specification
    .where(OrderSpecifications.hasStatus(req.getStatus()))
    .and(OrderSpecifications.amountAtLeast(req.getMinAmount()))
    .and(OrderSpecifications.createdAfter(req.getFromDate()));

Page<Order> orders = orderRepository.findAll(spec,
    PageRequest.of(req.getPage(), req.getSize(),
                   Sort.by(Sort.Direction.DESC, "createdAt")));

Key Points to Remember

  • 1Criteria API provides compile-time type safety; JPQL strings fail at runtime when field names change.
  • 2The Hibernate JPA metamodel generator (hibernate-jpamodelgen) produces entity_class_ files with SingularAttribute/ListAttribute fields.
  • 3Build predicates conditionally and combine with cb.and() for dynamic optional-filter search endpoints.
  • 4Spring Data Specifications are the recommended abstraction — cleaner than raw Criteria, composable with .and()/.or().
  • 5CriteriaQuery<T> vs CriteriaQuery<Tuple>: use Tuple for multi-column projections that do not map to a single entity.
  • 6For very complex reports, native SQL is often simpler than deep Criteria nesting — choose the right tool for readability.

Interview Questions

Sign in to ask Aria
1

What advantage does the Criteria API provide over JPQL strings?

EasyAmazon
2

How does the JPA metamodel (Order_) help build type-safe queries?

MediumNetflix
3

How would you implement a dynamic search endpoint with 5 optional filters using Specifications?

MediumShopify
4

What is the difference between JoinType.INNER and JoinType.LEFT in a Criteria JOIN and when would you use each?

HardGoogle
5

When would you choose native SQL over the Criteria API for a complex query?

MediumUber

Ask Aria about Criteria API

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…