Unravel โบ Java concepts โบ Persistence and JPA
Dynamic queries (Specifications, Example, Querydsl)
When search filters are optional (name, status, date range), you cannot write one fixed query method.
Learn these first
At a glance
Building a sandwich to order: each requested ingredient is a small, reusable piece, and you stack only the ones the customer asked for.
- 1A Specification<Order> is a function that adds a condition: (root, query, cb) -> cb.equal(root.get("status"), status)Small static methods return them, and and() / or() combine them.
- 2The repository extends JpaSpecificationExecutor<Order> and calls findAll(spec, pageable)A null specification means "no filter".
- 3Query by Example uses a probe object: fields that are set become conditions, with an ExampleMatcher choosing contains or ignore-caseQuerydsl does the same with a generated, type-safe query language.
- 4Pageable and Sort parameters add LIMIT, OFFSET and ORDER BY; Page also runs a count query, Slice skips it
- String field names like "status" are checked only at runtime
- Whitelist the sortable and filterable fields
- Each specification is invoked with the JPA Criteria API objects (Root, CriteriaQuery, CriteriaBuilder) and returns a Predicate
public final class OrderSpecs { public static Specification<Order> hasStatus(String status) { return (root, query, cb) -> status == null ? null : cb.equal(root.get("status"), status); } public static Specification<Order> createdAfter(LocalDate day) { return (root, query, cb) -> day == null ? null : cb.greaterThan(root.get("createdOn"), day); }}Page<Order> page = orders.findAll( Specification.where(OrderSpecs.hasStatus(status)).and(OrderSpecs.createdAfter(since)), PageRequest.of(0, 20, Sort.by("createdOn").descending()));- line 2 uses Specification, the core of Dynamic queries (Specifications, Example, Querydsl)
- line 5 uses Specification, the core of Dynamic queries (Specifications, Example, Querydsl)
- line 11 uses Specification, the core of Dynamic queries (Specifications, Example, Querydsl)
- line 12 uses PageRequest, the core of Dynamic queries (Specifications, Example, Querydsl)
Read the full explanation (about 1 min)
When search filters are optional (name, status, date range), you cannot write one fixed query method. Specifications and similar builders assemble the WHERE clause from the filters that are actually present.
A mental model
Building a sandwich to order: each requested ingredient is a small, reusable piece, and you stack only the ones the customer asked for.
How it works
A Specification<Order> is a function that adds a condition: (root, query, cb) -> cb.equal(root.get("status"), status). Small static methods return them, and and() / or() combine them.
The repository extends JpaSpecificationExecutor<Order> and calls findAll(spec, pageable). A null specification means "no filter".
Query by Example uses a probe object: fields that are set become conditions, with an ExampleMatcher choosing contains or ignore-case. Querydsl does the same with a generated, type-safe query language.
Pageable and Sort parameters add LIMIT, OFFSET and ORDER BY; Page also runs a count query, Slice skips it.
Going deeper
String field names like "status" are checked only at runtime. Use the generated metamodel (Order_.status) or Querydsl for compile-time safety, so renaming a field breaks the build instead of production.
Whitelist the sortable and filterable fields. Accepting any sort property from the client allows sorting by a sensitive or unindexed column.
Joins in specifications can duplicate rows or trigger the N+1 problem; use fetch joins and distinct, and check the generated SQL.
Under the hood
Each specification is invoked with the JPA Criteria API objects (Root, CriteriaQuery, CriteriaBuilder) and returns a Predicate. The repository builds one CriteriaQuery, applies the combined predicate, translates it to JPQL and then SQL through Hibernate, and executes it as a normal query.
Common mistakes
- Sorting by arbitrary client input.
- Offset pagination on huge tables (slow for deep pages).
- Unindexed filter columns.
- Stringly typed field names that break on refactoring.
How Unravel recognises it
When you paste code, Unravel looks for these signs of dynamic queries (specifications, example, querydsl). Spotting them yourself is a good habit when you read unfamiliar code:
- Types:
JpaSpecificationExecutorSpecificationExampleMatcherQueryByExampleExecutorQuerydslPredicateExecutorJPAQueryFactoryBooleanBuilderPageRequestCriteriaBuilder
Part of a learning path
- JPA and Hibernate in depth ยท previous: JPQL, native queries and the Criteria API ยท next: Optimistic and pessimistic locking
Related concepts
Frequently asked questions
What is Dynamic queries (Specifications, Example, Querydsl)?
When search filters are optional (name, status, date range), you cannot write one fixed query method. Specifications and similar builders assemble the WHERE clause from the filters that are actually present.
What are the most common mistakes with Dynamic queries (Specifications, Example, Querydsl)?
Sorting by arbitrary client input. Offset pagination on huge tables (slow for deep pages). Unindexed filter columns. Stringly typed field names that break on refactoring.
How can I see Dynamic queries (Specifications, Example, Querydsl) in real code?
Paste your own Java into Unravel and it will point out where dynamic queries (specifications, example, querydsl) appears, explain how it works at the detail level you choose, and draw the flow of the code around it.