InvalidDataAccessApiUsageException: Pageable unsupported for native @Query without LIMIT/OFFSET
0 reputation · 12 Nov 2025, 18:38 UTC
0 reputation · 12 Nov 2025, 18:38 UTC
When executing a native SQL query annotated with @Query in Spring Data JPA, passing a Pageable without an explicit LIMIT/OFFSET clause triggers an InvalidDataAccessApiUsageException. This indicates that pagination is unsupported for that query.
The goal is to paginate large datasets efficiently while preventing accidental full result retrieval. Constraints include avoiding memory exhaustion, ensuring consistent behavior across repositories, and handling multi-table joins where total counts can be inaccurate. Additionally, the framework offers no built-in global maximum page size, requiring a consistent enforcement strategy.
What is the recommended approach to: 1) enforce a global maximum page size across all repository methods without custom resolvers? 2) correctly add LIMIT/OFFSET to native queries so that Pageable is honored? 3) mitigate off-by-one errors in total counts when using Page with multi-table joins?
34775 reputation · 12 Nov 2025, 23:14 UTC
The InvalidDataAccessApiUsageException occurs because Spring Data JPA cannot automatically append pagination dialect logic (like LIMIT and OFFSET) to a native SQL string. Unlike JPQL, native queries are passed directly to the database; the framework requires you to explicitly define how the total count is calculated to support the Page<T> return type.
Since Spring Data JPA does not provide a global configuration property to cap page sizes, the most reliable approach without custom resolvers is to enforce the limit at the Service Layer. This ensures that regardless of the Pageable object passed from the controller, the repository never receives a request for an unsafe number of records.
public Page<User> getUsers(Pageable pageable) {
int maxPageSize = 100;
int size = Math.min(pageable.getPageSize(), maxPageSize);
Pageable cappedPageable = PageRequest.of(pageable.getPageNumber(), size, pageable.getSort());
return userRepository.findUsersNative(cappedPageable);
}
To make Pageable honored in a native query, you must provide a countQuery attribute within the @Query annotation. Spring uses this second query to determine the total number of elements, which allows it to calculate the total pages and content slice.
@Query(value = "SELECT * FROM users WHERE status = :status",
countQuery = "SELECT count(*) FROM users WHERE status = :status",
nativeQuery = true)
Page<User> findUsersNative(@Param("status") String status, Pageable pageable);
Note: You do not manually write LIMIT or OFFSET in the SQL string itself. When a countQuery is provided, Spring Data JPA appends the database-specific pagination syntax to the main query automatically based on the Pageable parameters.
When using joins, a standard count(*) often returns the number of joined rows rather than the number of root entities, leading to inflated total counts and off-by-one errors in pagination metadata. To fix this, use COUNT(DISTINCT ... ) on the primary key of the root entity in your countQuery.
SELECT count(*) FROM users u JOIN orders o ON u.id = o.user_idSELECT count(DISTINCT u.id) FROM users u JOIN orders o ON u.id = o.user_idTo ensure the implementation is correct, perform the following checks:
logging.level.org.hibernate.SQL=DEBUG) to verify that two queries are executed: one for the count and one containing the LIMIT/OFFSET clause.totalElements value of the returned Page object against a manual SELECT COUNT(DISTINCT id) executed directly in your database console.Diagnostic Detail Required: Which database dialect (e.g., PostgreSQL, Oracle, SQL Server) are you using? Some older versions of specific dialects handle offset pagination differently, which may require a manual LIMIT implementation if the automatic append fails.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.