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

Spring GraphQL list bounds: reject large arguments before reading rows

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

A list resolver needs an explicit maximum and a database-side limit; a GraphQL field selection alone does not bound row work.

Download Spring source kit

Validate the count at the resolver boundary

The fixture accepts limit 1 and returns the first north-tenant receipt after sorting by ID. It rejects 47 with a GraphQL error because its contract allows only 1 through 25. The test is intentionally small: the in-memory stream walks the ledger before limiting. A production query should enforce tenant, stable order and limit in SQL, plus a pagination cursor or offset contract. Keyset pagination explains how to continue a stable ordered scan without an ever-growing offset.

A client can ask for many aliased copies of the same field or nest expensive child selections under a bounded root list. A per-field limit is one control, not a complete operation cost budget. Decide maximum depth, total field work, timeouts and cancellation with measurements from real traffic. The source kit does not test those controls.

Keep ordering explicit

Without stable ordering, page boundaries can shift between requests and repeated rows can appear. The fixture sorts by receipt ID to make its one-page assertion deterministic. A real query should choose a unique tie-breaker and document what happens while rows are inserted or updated during pagination.

Checked excerpt

Java
private List<Receipt> receipts(DataFetchingEnvironment environment) {
    int limit = environment.getArgument("limit");
    if (limit < 1 || limit > 25) {
        throw new IllegalArgumentException("limit outside 1..25");
    }
    return ledger.values().stream()
        .filter(receipt -> receipt.tenant().equals(tenant(environment)))
        .sorted((left, right) -> left.id().compareTo(right.id()))
        .limit(limit).toList();
}

Verification boundary

The Java 21 / Spring Boot 4 source kit checks listIsTenantScopedAndBounded and oversizedListRequestReturnsGraphQlError.

Cost and limits

The checked in-memory implementation sorts all matching records, so its cost is O(n log n) time and O(n) storage before limit. A database-side indexed tenant-and-ID query can avoid scanning and sorting the full set, but must be measured on the target engine.

Common Mistakes

  • Do not mistake the requested selection set for a query-cost cap.
  • Do not apply a limit after loading every database row.
  • Do not paginate without a stable order and tie-breaker.

Read next

Spring Data keyset pagination: continue after the last stable identifier, Spring GraphQL schema nullability: trace one field failure through the response, Spring GraphQL errors: distinguish validation, resolver failure and nullable data, Spring method authorization: reject a cross-tenant read, Spring tests: separate business rules, wiring and transport.

spring
spring-boot
graphql
graphql-bounded-list
Storage details