Providing pagination for displaying large set of data will improve user experience and reduce the response time. Spring Boot comes with out-of-the-box solution for handling pagination, so let’s check how to implement it in an example application.
What we are going to build
In our example API we want to fetch time travellers on the "/api/timetravellers" endpoint, but because the list can be elongated, we need to paginate the results.
We are working with:
- the
TimeTravellerentity withidandnameproperties, TimeTravellerRepository,TimeTravellerServicefor fetching data,TimeTravellerControllerfor providing the endpoint.
Requirements
The following fragment of the pom.xml file shows dependencies used in the project:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// pom.xml … <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> … |
Enable pagination
To allow paginating requested data we have to make sure that TimeTravellerRepository extends the PagingAndSortingRepository interface:
|
1 2 3 4 5 |
//TimeTravellerRepository.java import org.springframework.data.repository.PagingAndSortingRepository; public interface TimeTravellerRepository extends PagingAndSortingRepository<TimeTraveller, Long> { } |
Now the TimeTravellerService method can accept Pageable instance as an argument and simply pass it when calling findAll() method on the repository:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//TimeTravellerService.java import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; … @Service public class TimeTravellerService { private TimeTravellerRepository travellerRepository; public TimeTravellerService(TimeTravellerRepository travellerRepository) { this.travellerRepository = travellerRepository; } … public Page<TimeTraveller> getTimeTravellers(Pageable pageable) { return travellerRepository.findAll(pageable); } … |
Check out the simple example test for this functionality in TimeTravellerServiceTest:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// TimeTravellerServiceTest.java … import in.keepgrowing.eternityproject.TimeTraveller; import in.keepgrowing.eternityproject.TimeTravellerRepository; import in.keepgrowing.eternityproject.TimeTravellerService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.*; @RunWith(MockitoJUnitRunner.class) public class TimeTravellerServiceTest { @Mock private TimeTravellerRepository travellerRepository; private TimeTravellerService travellerService; @Before public void setUp() { travellerService = new TimeTravellerService(travellerRepository); } … @Test public void getsPagedTimeTravellers() { int pageNumber = 0; int pageSize = 1; Pageable pageable = PageRequest.of(pageNumber, pageSize); TimeTraveller traveller = new TimeTraveller("James Cole"); Page<TimeTraveller> travellerPage = new PageImpl<>(Collections.singletonList(traveller)); when(timeTravellerRepository.findAll(pageable)).thenReturn(travellerPage); Page<TimeTraveller> travellers = timeTravellerRepository.findAll(pageable); assertEquals(travellers.getNumberOfElements(), 1); } |
Get paginated results
We are going to handle the following request:
|
1 |
http://localhost:8080/api/timetravellers?page=0&size=5&sort=id,asc |
The controller endpoint looks straightforward:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//TimeTravellerController.java … import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @RestController @RequestMapping("api/timetravellers") public class TimeTravellerController { … @GetMapping public Page<TimeTraveller> getTimeTravellers(Pageable pageable) { return timeTravellerService.getTimeTravellers(pageable); } … |
And below you can find the complementary test for the controller endpoint:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
//TimeTravellerControllerTest … import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.web.config.EnableSpringDataWebSupport; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringRunner.class) @WebMvcTest(TimeTravellerController.class) @EnableSpringDataWebSupport public class TimeTravellerControllerTest { private final String apiPath = "/api/timetravellers"; @MockBean private TimeTravellerService travellerService; @Autowired private MockMvc mvc; private JacksonTester<TimeTraveller> travellerJacksonTester; @Before public void setUp() { JacksonTester.initFields(this, new ObjectMapper()); } @Test public void getsTimeTravellers() throws Exception { TimeTraveller traveller = new TimeTraveller("James Cole"); Page<TimeTraveller> page = new PageImpl<>(Collections.singletonList(traveller)); given(projectService.getTimeTravellers(any())).willReturn(page); mvc.perform(get(apiPath) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].name", is(traveller.getName()))); } … |
After you had the time traveller created in your API, the Postman output for http://localhost:8080/api/timetravellers?page=0&size=5&sort=id,asc will look like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
{ "content": [ { "name": "James Cole", "id": 1 } ], "pageable": { "sort": { "sorted": true, "unsorted": false }, "pageSize": 5, "pageNumber": 0, "offset": 0, "paged": true, "unpaged": false }, "last": true, "totalElements": 1, "totalPages": 1, "first": true, "sort": { "sorted": true, "unsorted": false }, "numberOfElements": 1, "size": 5, "number": 0 } |
Photo by Allef Vinicius on StockSnap

Simply wish to say your article is as astounding. The clearness in your put up is just spectacular and i could think you are a professional on this subject. Well with your permission allow me to clutch your feed to stay up to date with impending post. Thanks a million and please carry on the rewarding work.
You have done a great Job. Well done!
I really liked this article. It solves my problem of creating and passing pagable object.