Querying content (QueryBuilder & JCR-SQL2)
Purpose: Find content efficiently: choosing the API, writing index-backed queries, and avoiding traversal warnings.
Who this page is for
| Audience | Why it matters to you |
|---|---|
| Backend engineers | Search and listing features |
| All engineers | Debugging content questions |
First question: do you even need a query?
If you know the path shape (/content/phi/plans/*), iterate children — it is faster and index-free. Query when matching by property values across unknown locations.
The APIs
| API | Style | Use |
|---|---|---|
| QueryBuilder | Map of predicates, AEM-specific | App code — readable, supports facets, pagination |
| JCR-SQL2 | SQL-ish standard | Tooling, complex conditions, ad hoc analysis |
| XPath | Legacy | Read-only knowledge — you'll meet it in old code |
QueryBuilder example — gold-tier plan pages
type=cq:Page
path=/content/phi/plans
1_property=jcr:content/isGoldTier
1_property.value=true
orderby=@jcr:content/jcr:title
p.limit=20
(Test interactively at /libs/cq/search/content/querydebug.html.)
Indexes or it didn't happen
Every production query must be backed by an Oak index; unindexed queries fall back to traversal (walking nodes) and log Traversal query warnings before eventually being blocked at a node-count limit.
| Rule | Why |
|---|---|
| Check the plan with "Explain Query" (Operations → Diagnosis) | Confirms which index serves it |
| Add custom property indexes for your query patterns | /oak:index definitions, shipped as code |
| Constrain path + type in every query | Smaller index cost, better plans |
| Paginate (p.limit / offset or keyset patterns) | Never load unbounded result sets |
Common mistakes
p.limit=-1(unbounded) in application code — always cap.- Ordering by an unindexed property — sorts in memory.
- Using queries where a fixed path lookup works — the #1 avoidable query load.