Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring Data JPA Specifications with a mandatory tenant predicate

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Compose optional search filters around a tenant condition that callers cannot remove.

Separate optional filters from mandatory scope

A back-office receipt search accepts an optional status and a date range. The tenant condition is not optional. Build it inside the service from the authenticated identity, then combine user-selected filters with it. A Specification describes a predicate; it does not decide whether a principal may read the tenant. Resolve that decision first through tenant identity checks.

Make the query bounded

Passing an unbounded Pageable does not protect the database. Cap the requested page size, sort by createdAt and id, and decide whether the UI needs a total count. A Slice can omit the count when the UI only needs a next-page flag. An index beginning with tenantId and supporting the chosen filter and order can matter more than the Java predicate syntax.

Test composition, not just each piece

Seed receipts for two tenants with the same status and timestamp. Search with status supplied and absent; both results must stay inside the authenticated tenant. Then test an empty date interval and a malicious tenant ID supplied in request data. The service must ignore that caller-supplied scope. Inspect the generated SQL for repeated joins before adding more Specifications.

Implementation contract

Java
static Specification<ReceiptEntity> visibleReceipts(
        UUID authenticatedTenantId, ReceiptStatus requestedStatus) {
    Specification<ReceiptEntity> tenant = (root, query, builder) ->
        builder.equal(root.get("tenantId"), authenticatedTenantId);
    if (requestedStatus == null) return tenant;
    return tenant.and((root, query, builder) ->
        builder.equal(root.get("status"), requestedStatus));
}

Cost and verification

Predicate composition itself is small. Database work depends on selectivity, indexes, joins, and whether a count query is requested. An optional filter that prevents an index range scan can dominate the endpoint's cost.

Common Mistakes

  • Do not accept the tenant ID from a request field as an authorization decision.
  • Do not return an unbounded search because the Specification reads cleanly.
  • Do not assume individually tested predicates prove the combined query is tenant-safe.

Read next

Spring JWT tenant claims: reject missing or malformed ownership before conversion, Spring Data JPA Page versus Slice: pay for totals only when needed, Spring API pagination: bounded requests and immutable snapshots, Spring JdbcTemplate tenant predicates: put ownership in the SQL query, Spring Data JPA count query for a joined Page.

spring
spring-data-jpa
jpa-specification-tenant-scope
Storage details