How to create Pagination in a Spring API

Introduction

In this tutorial, we will learn how to create pagination in a Spring Boot REST API to serve our API consumers data effectively.

What do we create?

In previous tutorials, we created a basic REST API with various methods to serve a resource: Lemonade.

When retrieving the entire list of entities, we notice that the dataset can grow significantly. This becomes problematic when dealing with thousands of entities, as response times increase, and memory usage becomes inefficient.

Get elements without pagination Postman

A solution to our problem is to divide the large set of data into smaller, manageable chunks called “pages. It helps efficiently retrieve and display a subset of data at a time, rather than loading the entire dataset all at once, which can be resource-intensive and slow.

This solution is called Pagination and is often used when we are facing a lot of data, for instance when we are browsing products in an online store or posts in a social media platform.

When discussing Pagination, the following components are usually involved:

  • Page: a subset of the entire dataset 
  • Page Size: the number of items displayed per page (for instance, 50)
  • Current Page: the page number currently being viewed (for instance, page 1 or page 2).
  • Offset: the starting point for retrieving data (for instance, we receive data starting of number 100)
  • Total Elements: the total number of items in the dataset
  • Total Pages: the number of pages available

Even though it may sound complex, the implementation is straightforward. In this tutorial, we will use an in-memory database and only the Spring Boot Starter Web dependency to keep things simple.

How do we create it?

You can use Spring Initializr to quickly generate a Spring Boot project with Maven or Gradle. We can use the following configurations:

  • Group: com.example
  • Artifact: explainjavaRestAPI
  • Packing: jar
  • Java version: any
  • Dependencies: Spring Web (to create a REST API)
Spring REST API Initializr configuration

We will split our application into four layers: Domain, Repository, Service, and UI. Each layer has well-defined responsibilities. For our application, each layer represents a class, but later we will have multiple classes for each layer.

You will receive a ready-to-use project. All the configurations are already written in the build.gradle file, and the ExplainjavaRestApiApplication start class is already created.

In the next step, we will create a package for each layer of our application: Domain, Repository, Service, Controller, and a Utils package for adding a class to pack our response

First, we create a model class. BTW, you can check our tutorial on integrating the Lombok Project to see how to get rid of a lot of boilerplate code from model classes.

				
					public class Lemonade {

    private Long id;
    private String name;
    private String description;


    public Lemonade(Long id, String name, String description) {
        this.id = id;
        this.name = name;
        this.description = description;
    }
    
    //getters and setters
    
}
				
			

Next, we can add a Repository class. We can initialize some items in the constructor, and then add two methods: one for receiving a subset of items and another one for counting the total number of items from our list.

				
					@Repository
public class LemonadeRepository {

    private final List<Lemonade> lemonades = new ArrayList<>();

    public LemonadeRepository() {
        for (long i = 1; i <= 100; i++) {
            lemonades.add(new Lemonade(i, "Lemonade: " + i, "Description" + i));
        }
    }

    public List<Lemonade> findPaginated(int offset, int limit) {
        return lemonades.stream()
                .skip(offset)
                .limit(limit)
                .collect(Collectors.toList());
    }

    public long count() {
        return lemonades.size();
    }
}
				
			

This time, we are not returning a List of items; instead, we need to create a DTO object to hold information about our list. We can add this class in the utils package.

				
					public class PaginatedResponse<T> {
    
    private List<T> content;
    private int page;
    private int size;
    private long totalElements;
    private int totalPages;

    public PaginatedResponse(List<T> content, int page, int size, long totalElements, int totalPages) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
        this.totalPages = totalPages;
    }
    
    //getters and setters
    
}
				
			

Next, we will work on the service layer. We need our repository to populate the PageResponse.

				
					@Service
public class LemonadeService {

    @Autowired
    private LemonadeRepository lemonadeRepository;

    public PaginatedResponse<Lemonade> getPaginatedLemonades(int page, int size) {
        int offset = page * size;
        List<Lemonade> lemonades = lemonadeRepository.findPaginated(offset, size);

        long totalElements = lemonadeRepository.count();
        int totalPages = (int) Math.ceil((double) totalElements / size);

        return new PaginatedResponse<>(lemonades, page, size, totalElements, totalPages);
    }
}
				
			

The final step is to create the controller. This time, we need to receive two things from our consumers: the current page and the size. We can receive this parameters using the @RequestParam annotation:

				
					@RestController
@RequestMapping("/api/lemonades")
public class LemonadeController {

    @Autowired
    private LemonadeService lemonadeService;

    @GetMapping
    public PaginatedResponse<Lemonade> getLemonades(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return lemonadeService.getPaginatedLemonades(page, size);
    }

}
				
			

Let’s test our pagination using Postman. If you are not sure how to use this tool, we created a tutorial to explain it.

We can pass anything we want as params, for instance we can set a size of 10 and iterate through totalPages to get all elements.

Pagination request test with Postman

Conclusion

In this tutorial we successfully implemented pagination in a REST API using Spring Boot with an in-memory repository.