Efficient Pagination in JHipster‑Generated Spring Boot Apps Using Pageable
Learn how JHipster‑generated Spring Boot apps use Pageable to push pagination into Hibernate, verify the SQL, and manage the extra COUNT query trade‑off.
20 Feb 2026, 05:13 UTC

Problem: Pulling whole tables kills performance
When a JHipster-generated Angular/React UI requests a list of entities (for example, /api/employees), the default controller method returns a Page object. If the underlying repository method does not properly translate the Pageable argument, Hibernate falls back to loading every row into memory and then slicing it in Java. On tables with thousands of rows this causes noticeable latency, higher CPU usage, and unnecessary network traffic.
Thesis: Let Hibernate do the work
JHipster’s code generators already wire Pageable through the REST layer to Spring Data JPA repositories. When you keep the generated signatures intact, Hibernate adds LIMIT and OFFSET clauses to the SQL, executing the pagination at the database level. The only extra work is a separate COUNT query to determine the total number of elements.
How JHipster sets it up
Running the JHipster entity sub‑generator creates three artefacts for each entity:
EmployeeResource– a Spring MVC@RestControllerwith a method like:
@GetMapping("/api/employees")
public ResponseEntity<Page<EmployeeDTO>> getAllEmployees(Pageable pageable) {
Page<EmployeeDTO> page = employeeService.findAll(pageable);
return ResponseEntity.ok().headers(PaginationUtil.generatePaginationHttpHeaders(page, UriComponentsBuilder.fromPath("/api/employees"))).body(page);
}
EmployeeService– delegates to the repository:
@Transactional(readOnly = true)
public Page<EmployeeDTO> findAll(Pageable pageable) {
return employeeRepository.findAll(pageable).map(employeeMapper::toDto);
}
EmployeeRepository– extendsJpaRepository<Employee, Long>and inherits the methodPage<Employee> findAll(Pageable).
Because the repository method is not overridden, Spring Data JPA creates a query that respects the Pageable instance: sort properties become ORDER BY, page number and size become LIMIT ? OFFSET ?.
Worked example: verifying the generated SQL
Assume you have a JHipster monolith with the default H2 dev database. Follow these steps to confirm that pagination is pushed down to Hibernate:
- Enable SQL logging – edit
src/main/resources/config/application-dev.ymland add:
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
- Run the application (requires read/write access to the project directory):
# From the project root
./mvnw spring-boot:run
- Request a page – using
curlor a browser:
curl -v "http://localhost:8080/api/employees?page=0&size=20&sort=firstName,asc"
- Inspect the console output – you should see a statement similar to:
Hibernate:
select
employee0_.id as id1_0_,
employee0_.first_name as first2_0_,
employee0_.last_name as last3_0_,
...
from
employee employee0_
order by
employee0_.first_name asc
limit ? offset ?
The presence of limit and offset confirms that the database is doing the pagination. A second statement with select count(*) as col_0_ from employee employee0_ is the inevitable COUNT query.
Trade‑off: the extra COUNT query
Every Pageable call triggers two round‑trips: one for the data, one for the total. For very large tables the COUNT can become expensive if there are no suitable indexes on the WHERE clauses (if you add filters). To mitigate:
- Ensure columns used in
sortor custom@Queryfilters are indexed. - If the UI does not need the total (e.g., infinite scroll), replace the return type with
List<EmployeeDTO>and callemployeeRepository.findAll(pageable.getSort())– this eliminates the COUNT but you lose page‑size metadata. - Adjust the default page size to match UI expectations; JHipster’s scaffold uses
PageRequest.of(0, 20). Changing it to 50 or 100 reduces the number of requests but increases payload size.
Limitation: overriding repository methods can break pagination
If you add a custom method like:
@Query("select e from Employee e where e.department = :dept")
List<Employee> findByDepartment(@Param("dept") String dept);
and call it from the service, the return type is List, not Page. The controller will still receive a Pageable argument, but it will be ignored, causing the service to load all matching rows before slicing in Java. Always verify that any overridden repository method either keeps the Pageable parameter and returns Page<T> or is deliberately used for non‑paginated use cases.
Actionable closing
- Keep the generated repository method signatures unchanged unless you have a specific need to project or filter.
- Run the application with
spring.jpa.show-sql=trueand check the logs forLIMITandOFFSETafter each paginated request. - If you observe a high‑cost COUNT, examine the execution plan (
EXPLAIN ANALYZE) and add missing indexes. - Tailor the default page size in
application*.ymlunderjhipster.clientApp.nameor directly in the controller if you want a different default. - When the total count is unnecessary, switch to a
List return type and drop the COUNT, but document the behavioural change for API consumers.
By trusting the Pageable contract that JHipster already wires, you let Hibernate push pagination to the database, keeping response times predictable even as your data set grows.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.