As part of the REST with Spring Series, this post will cover the fundamentals of implementing pagination in REST API. We are going to focus on building REST pagination in Spring MVC using Spring Boot and Spring Data.
Introduction
Pagination is a mechanism for handling the big result set in any type of application. Implementing pagination in REST API is not different but need some extra thought process. Providing a fluent and efficient pagination for REST API could increase user experience and help to build efficient and fast REST API. We are using Spring Boot for our examples but this can be build without it.
1. Resource vs Representation
Before we start designing our pagination API, we need to have a clear understanding of page as a resource or a representation of the resource. There are numbers of fundamentals we need to keep in mind
- The page is not a resource in REST but its a property of the request.
Let’s take an example of building pagination for a resource name Product, on a high level we do have following three options to build the pagination.
- Take product as a resource and use query strings to handle pagination along with other parameters like sorting etc. (e.g.
http://domainname/products?page=1
). - The second option is to use the page as a resource and query string for sorting. (e.g.
http://domainname/products/page/1?sort_by=date
). - use page as resource and URL part for sorting. (e.g.
http://domainname/products/date/page/1
)
With above question in mind, let’s try to answer few of the question which will be helpful while designing pagination for the REST API.
- Do you see the page as a resource of the products within the page as the resource?
Keep in mind that REST API is not built around any predefined rules or specifications, all the three above options are valid and based on the answer to the above question. If we think the page as a resource, option three is a valid choice (query to page 1 will give page 1 and 2 give page 2), but if we say that products on the page are the resource than option 3 is no longer valid (product might change in the future on page 1,2 etc.).Personally, I will choose option 1 since for me page is not a resource, it’s a property of the request.
2. Discoverability
Discoverability helps to make RESTful API more useful and elegant. Making the REST API discoverable is often overlooked. Here is a high-level summary of the REST API discoverability.
- With this feature, REST API providing full URI’s in the responses to the client means that no client will ever need to “compose” a URI.
- Client API become independent of the URI structure.
- With above 2 points, API is more flexible and allow the developer to change URI schema without breaking API. (Remember, API provides all URI, they are not created dynamically by the client API).
Discoverability is closely related to HATEOAS in the REST API. REST API pagination discoverability will pass on "next"
,"previous"
,"first"
and "last"
link as part of the response data. We are coving how to add this feature to your API during pagination.
3. Pagination Design Considerations
Let’s quickly cover some of the main points while building your REST API pagination interface.
3.1. Limit
Limit allow API and client to control the number of results requested in the resultset. By passing the limit
parameter, you can specify how many items you want each page to return.API can configure default limit but should allow the client to specify a limit.
http://hostname/products?page=1&limit=50
In the above request, the client is setting the limit as 50. Be careful while allowing the customer to set limit
parameter as a very high number of limit can degrade API performance. REcommendation to have a maximum allowed limit during the API design.
3.1. Sorting
Sorting always goes side by side with searching and pagination. While designing the REST API, provide the flexibility to let client specify the sorting option while getting the results back from the API.The recommendation is to use sort_by=[attribute name]-[asc/desc] pattern while designing your API.API designer should specify the allowed attribute name as the sort parameter. For example, you can use ?name-asc
to sort by products by name or ?name-desc
to sort in reverse.
4. Maven Dependencies
We covered all the basic stuff while working on REST Pagination in Spring. We are using following technology stack for this post, but it can be implemented on any other technology provided you are following all the basic principles while designing.
- Spring Boot
- JPA.
- Spring Data REST
One of the reasons to use Spring Data REST for this post is the out of box features supported by Data REST API.
We will add following dependencies in our pom.xml
- Spring Boot JPA
- Spring Boot Data REST
- HATEOS and Web
<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-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
4.1 REST Controller
@RestController
public class ProductRESTController {
@Autowired
private ProductService productService;
@Autowired private EntityLinks links;
@GetMapping(value = "/products", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity < PagedResources < ProductEntity >> AllProducts(Pageable pageable, PagedResourcesAssembler assembler) {
Page < ProductEntity > products = productService.findAllProducts(pageable);
PagedResources < ProductEntity > pr = assembler.toResource(products, linkTo(ProductRESTController.class).slash("/products").withSelfRel());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Link", createLinkHeader(pr));
return new ResponseEntity < > (assembler.toResource(products, linkTo(ProductRESTController.class).slash("/products").withSelfRel()), responseHeaders, HttpStatus.OK);
}
private String createLinkHeader(PagedResources < ProductEntity > pr) {
final StringBuilder linkHeader = new StringBuilder();
linkHeader.append(buildLinkHeader(pr.getLinks("first").get(0).getHref(), "first"));
linkHeader.append(", ");
linkHeader.append(buildLinkHeader(pr.getLinks("next").get(0).getHref(), "next"));
return linkHeader.toString();
}
public static String buildLinkHeader(final String uri, final String rel) {
return "<" + uri + ">; rel=\"" + rel + "\"";
}
}
Let’s quickly cover few important points in the above code.
- We used Pageable as one of the parameters of the controller. This will help return a Page rather than a List.
- Pageable has all the required pagination information.
- Pageable works really well with Spring JPA and handle pagination transparently.
4.2 Previous and Next Links
Each paged response will return links to the previous and next pages of results based on the current page using the IANA defined link relations prev
and next
. If you are currently on the first page of results, however, no prev
link will be rendered.
Let’s look at the following example
curl http://localhost:8080/products
{
"_embedded": {
"productEntities": [
...data...
]
},
"_links": {
"first": {
"href": "http://localhost:8080/products?page=0&size=20"
},
"self": {
"href": "http://localhost:8080/products"
},
"next": {
"href": "http://localhost:8080/products?page=1&size=20"
},
"last": {
"href": "http://localhost:8080/products?page=4&size=20"
}
},
"page": {
"size": 20,
"totalElements": 100,
"totalPages": 5,
"number": 0
}
}
Let’s take a deep look at some of the interesting facts in the response data
- The
next
link points to the next page. Thelast
link points to the last result set. - The
self
link serves up the whole collection. - The bottom
page
section provides information about the pagination including page size, total results, total pages and current page number.
4.2 Using Link Header
HTTP Headers are critical aspects of the REST APIs.HTTP link header can also be used to pass pagination information to the client. With the above test, the system will return following additional information as part of the Link HTTP header.
Link →<http://localhost:8080/products?page=0&size=20>; rel="first", <http://localhost:8080/products?page=1&size=20>; rel="next"
Let’s break that down. rel="next"
says that the next page is page=2
.rel="first"
says that the first page Always reliespage=2.
on these link relations provided to you. Don’t try to guess or construct your own URL. Spring PagedResource provide all this information as part of the result, we only need to make sure to build correct HTTP header from this information. In our controller example, we are building our header in createLinkHeader method.
private String createLinkHeader(PagedResources < ProductEntity > pr) {
final StringBuilder linkHeader = new StringBuilder();
linkHeader.append(buildLinkHeader(pr.getLinks("first").get(0).getHref(), "first"));
linkHeader.append(", ");
linkHeader.append(buildLinkHeader(pr.getLinks("next").get(0).getHref(), "next"));
return linkHeader.toString();
}
public static String buildLinkHeader(final String uri, final String rel) {
return "<" + uri + ">; rel=\"" + rel + "\"";
}
[pullquote align=”normal”]Github has the best example of using the link header for pagination [/pullquote].
Summary
In this post, we learned how to implement REST pagination in Spring and Spring Boot. We discussed how to structure the response and the importance of using Link HTTP header in the REST API response.
The implementation of all these examples and code snippets can be found in the GitHub project
PagedResources pr = assembler.toResource(products, linkTo(ProductRESTController.class).slash(“/products”).withSelfRel());
I didn’t find the words ‘linkTo’, this is written ?
linkTo is part of the static import, please refer to the complete example available on the GitHub
https://github.com/umeshawasthi/javadevjournal/blob/master/spring/rest-pagination/src/main/java/com/javadevjournal/rest/controller/ProductRESTController.java