Mastering JHipster Pagination: From Generated Pageable to Custom @Query
Learn how JHipster’s auto‑generated PagingAndSortingRepository works, how to add complex filters with @Query, and how to verify the pagination JSON payload from your Angular or React front‑end.
22 Feb 2026, 15:31 UTC

Why Pagination Matters in a JHipster App
When a JHipster project is generated, the data layer is wired to Spring Data JPA’s PagingAndSortingRepository or JpaRepository. These interfaces expose a findAll(Pageable) method that automatically translates to a SQL LIMIT / OFFSET query. The front‑end receives a JSON payload with totalElements, totalPages, and the current content slice. This is the default “plumbing” that lets you ship a responsive UI without writing any paging logic yourself.
Generating the Repository
During the JHipster wizard, the selected entity (e.g., Customer) gets a repository interface under src/main/java/com/example/app/repository:
package com.example.app.repository;
import com.example.app.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
// findAll(Pageable) is inherited from JpaRepository
}
Notice the JpaSpecificationExecutor mix‑in; it gives you a findAll(Specification, Pageable) overload for dynamic predicates. If you need a static filter, just add a method name following Spring Data conventions or annotate with @Query.
Adding a Complex Filter with @Query
Suppose you want to expose customers whose last purchase was in the last month and whose status is "ACTIVE". The generated repository only supports simple queries, so you’ll add a custom method:
@Query("SELECT c FROM Customer c WHERE c.status = 'ACTIVE' AND c.lastPurchase > :cutoff")
Page<Customer> findActiveRecent(@Param("cutoff") LocalDate cutoff, Pageable pageable);
Place this method in the same interface. No additional implementation file is required because Spring Data will generate the query at runtime. If you need a native SQL or stored‑procedure call, create a class named CustomerRepositoryImpl in the same package and implement the interface manually, but keep the Pageable signature to preserve pagination.
Consuming the Paginated API in a SPA
JHipster’s REST layer automatically exposes the repository methods via a controller. For the custom query, add a new endpoint:
@RestController
@RequestMapping("/api/customers")
public class CustomerResource {
private final CustomerRepository customerRepository;
public CustomerResource(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@GetMapping("/active-recent")
public ResponseEntity<Page<CustomerDTO>> getActiveRecent(
@RequestParam(required = false) LocalDate cutoff,
Pageable pageable) {
Page<Customer> page = customerRepository.findActiveRecent(cutoff, pageable);
return ResponseEntity.ok().body(page.map(CustomerDTO::fromEntity));
}
}
In Angular, the service call might look like this:
getActiveRecent(page: number, size: number, cutoff: string): Observable<PageResponse<CustomerDTO>> {
const params = new HttpParams()
.set('page', page.toString())
.set('size', size.toString())
.set('cutoff', cutoff);
return this.http.get<PageResponse<CustomerDTO>>('/api/customers/active-recent', { params });
}
The PageResponse interface mirrors the JSON structure:
interface PageResponse<T> {
content: T[];
totalElements: number;
totalPages: number;
size: number;
number: number;
}
Verifying Pagination Works
- Run the Spring Boot application:
./mvnw spring-boot:run(Linux/macOS) ormvnw.cmd spring-boot:run(Windows). - Use
curlto hit the endpoint:curl -G http://localhost:8080/api/customers/active-recent --data-urlencode "page=0" --data-urlencode "size=5" --data-urlencode "cutoff=2026-01-01". - Check the JSON: it should contain a
contentarray with up to five customers,totalElementsindicating the full result set, andtotalPagescalculated fromsize. - In the Angular console, log the response to confirm
page.totalElementsmatches the API.
Trade‑offs & Limitations
Using Spring Data’s Pageable keeps the code concise, but it relies on the provider’s default sorting (usually the primary key). If you need custom sorting or filtering that involves multiple joins, the generated findAll(Pageable) may produce inefficient queries or trigger N+1 selects. In those cases:
- Prefer
JpaSpecificationExecutorwithSpecificationobjects that build criteria queries. - Annotate the repository method with
@EntityGraphto fetch associations eagerly. - For very complex scenarios, replace the repository method with a native query or a stored procedure and manually map the result to a
Pageobject.
Another limitation is that the JSON payload always includes the full Page metadata. If the front‑end only needs the slice, you can create a custom DTO that omits totalElements and totalPages, but you lose the ability to calculate the total number of pages client‑side.
Practical Checklist Before Deploying
- Confirm the repository interface contains the desired
Pageablemethod. - Write a unit test that passes a
PageRequest.of(0, 10)and asserts the returnedPagesize. - Use Postman or curl to hit the endpoint with different
pageandsizevalues and verifytotalElementsstays constant. - Inspect the generated SQL with
spring.jpa.show-sql=trueor a profiler to ensure no N+1 queries. - Deploy to a staging environment and monitor the database load under realistic pagination traffic.
Conclusion
JHipster’s default pagination is a powerful starting point that saves you from boilerplate code. When your domain requires more sophisticated filtering, a lightweight @Query or Specification is all you need. By validating the JSON response and watching for performance pitfalls, you can keep the UI snappy and the back‑end efficient.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.