Paginating Data in Spring Boot GraphQL
GraphQL APIs can return large datasets, and without pagination, performance issues may arise. Spring Boot provides excellent support for GraphQL through the spring-boot-starter-graphql dependency. Let us delve into understanding spring boot graphql pagination and how it enhances data fetching efficiency in modern applications.
1. What is GraphQL?
GraphQL is an open-source query language for your API and a runtime for executing those queries with your existing data. Developed by Facebook, it allows clients to request exactly the data they need, reducing over-fetching and under-fetching common with traditional REST APIs. Instead of having multiple endpoints for different data sets, GraphQL consolidates data fetching into a single flexible endpoint. Clients can compose complex queries, nest resources, and request related data in one round trip.
1.1 What is Pagination?
Pagination is a technique used in APIs to break large datasets into smaller, more manageable chunks. This improves performance, reduces memory consumption, and avoids overloading clients and servers with too much data at once. In GraphQL pagination, it is commonly implemented using either:
- Page-based pagination: where data is divided by pages (e.g., page number and size).
- Cursor-based pagination: where data is fetched using a cursor or ID from the last record of the previous batch.
1.1.1 Use Cases and Importance of Pagination
- Pagination helps divide large datasets into manageable chunks, improving both server and client performance.
- It minimizes memory consumption and reduces the data transferred over the network.
- It enhances user experience by loading content faster and allowing incremental content access (e.g., infinite scroll or pages).
- Pagination reduces database load by limiting the number of rows processed in a single query.
1.1.2 Page-based vs Cursor-based Pagination
- Page-based Pagination: Ideal for simple UI needs such as numbered pages or when jumping to a specific page is required.
- Best used when data changes infrequently or consistency isn’t a concern.
- Cursor-based Pagination: Suitable for high-performance scenarios and large datasets where sorting is consistent and stable.
- Prevents issues with data shifting during pagination, making it reliable in dynamic datasets (e.g., social feeds, logs).
2. Code Example
This section walks through a complete Spring Boot GraphQL application with support for page-based pagination using an H2 in-memory database. Additionally, we show how to fetch results after a certain ID using a custom repository method.
2.1 Setting up the Project
Start by adding the required Spring Boot and GraphQL dependencies to your Maven pom.xml file.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
2.2 Create support Classes
Define a simple JPA entity Book with fields for ID, title, and author.
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String author;
// Constructors
public Book() {}
public Book(String title, String author) {
this.title = title;
this.author = author;
}
// Getters and setters
}
Create the POJO class which will encapsulate paginated results with metadata.
import java.util.List;
public class BookPage {
private List<Book> content;
private int totalPages;
private long totalElements;
private int currentPage;
private int pageSize;
public BookPage() {}
public BookPage(List content, int totalPages, long totalElements, int currentPage, int pageSize) {
this.content = content;
this.totalPages = totalPages;
this.totalElements = totalElements;
this.currentPage = currentPage;
this.pageSize = pageSize;
}
// Getters and Setters
}
2.3 Create the Repository Interface
Create a Spring Data JPA repository with a custom query for pagination starting after a specific ID.
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
@Query("SELECT b FROM Book b WHERE b.id > :afterId ORDER BY b.id ASC")
List<Book> findTopNAfterId(@Param("afterId") Long afterId, Pageable pageable);
}
2.3.1 Code Explanation
The BookRepository interface extends JpaRepository to provide CRUD operations for the Book entity. It includes a custom query method, findTopNAfterId, which retrieves a list of Book records where the ID is greater than a given afterId. This method uses a JPQL query annotated with @Query to filter and order results by ascending ID, supporting cursor-based pagination. The Pageable parameter controls the number of results returned, enabling efficient slicing of data sets in a GraphQL pagination scenario.
2.4 GraphQL Pagination Schema
Define the GraphQL schema in schema.graphqls to support a paginated books query with optional cursor-based support.
# schema.graphqls
type Book {
id: ID
title: String
author: String
}
type BookPage {
content: [Book]
totalPages: Int
totalElements: Int
currentPage: Int
pageSize: Int
}
type Query {
books(page: Int, size: Int, afterId: ID): BookPage
}
2.4.1 Code Explanation
The schema.graphqls file defines the GraphQL schema for handling book-related queries with pagination support. It introduces a Book type representing individual books with fields like id, title, and author. To support pagination, the BookPage type wraps a list of books under content along with metadata such as totalPages, totalElements, currentPage, and pageSize. The Query type exposes a books query that accepts optional arguments page, size, and afterId, enabling both page-based and cursor-based pagination in the Spring Boot GraphQL application.
2.5 Implementing Pagination Resolver
Implement the query resolver to call the custom repository method and return a BookPage.
@Component
public class BookQueryResolver implements GraphQLQueryResolver {
@Autowired
private BookRepository bookRepository;
public BookPage getBooks(Integer page, Integer size, Long afterId) {
Pageable pageable = PageRequest.of(page != null ? page : 0, size != null ? size : 5);
List<Book> books;
long totalElements;
int totalPages;
if (afterId != null) {
books = bookRepository.findTopNAfterId(afterId, pageable);
totalElements = books.size();
totalPages = (int) Math.ceil((double) totalElements / pageable.getPageSize());
} else {
Page<Book> bookPage = bookRepository.findAll(pageable);
books = bookPage.getContent();
totalElements = bookPage.getTotalElements();
totalPages = bookPage.getTotalPages();
}
return new BookPage(books, totalPages, totalElements, pageable.getPageNumber(), pageable.getPageSize());
}
}
2.5.1 Code Explanation
The BookQueryResolver class is a Spring component that implements GraphQLQueryResolver to handle GraphQL queries. It defines the getBooks method, which supports both page-based and cursor-based pagination. The method accepts optional parameters: page, size, and afterId. If afterId is provided, it uses the custom repository method findTopNAfterId to return a slice of books starting after a specific ID, enabling cursor-based pagination. Otherwise, it uses the standard findAll method with PageRequest to return a Page object for page-based pagination. The response is wrapped in a BookPage object containing the book list, total pages, total elements, current page, and page size, making it suitable for GraphQL pagination implementations.
2.6 Create a Controller Class
Although optional for GraphQL, you can include a REST controller for testing or additional exposure.
@RestController
@RequestMapping("/api")
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping("/books")
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
}
2.7 Create a Main Class and Adding Mock Data in H2
Create the Spring Boot entry point and preload mock data into the H2 database using the CommandLineRunner.
@SpringBootApplication
public class BookServiceApplication implements CommandLineRunner {
@Autowired
private BookRepository bookRepository;
public static void main(String[] args) {
SpringApplication.run(BookServiceApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
bookRepository.save(new Book("The Hobbit", "J.R.R. Tolkien"));
bookRepository.save(new Book("1984", "George Orwell"));
bookRepository.save(new Book("To Kill a Mockingbird", "Harper Lee"));
bookRepository.save(new Book("The Catcher in the Rye", "J.D. Salinger"));
bookRepository.save(new Book("The Great Gatsby", "F. Scott Fitzgerald"));
}
}
2.8 application.properties Configuration
Below is the minimal configuration required in src/main/resources/application.properties for setting up the Spring Boot application with GraphQL and JPA:
# Server configuration server.port=8080 # H2 Database configuration spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true # Hibernate and JPA configuration spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create-drop # GraphQL configuration spring.graphql.schema.locations=classpath:graphql/ spring.graphql.graphiql.enabled=true spring.graphql.graphiql.path=/graphiql
You can access the GraphQL Playground at http://localhost:8080/graphiql after starting the application.
2.9 Code Run and Output
Once the application is running, you can test it using GraphQL Playground at http://localhost:8080/graphiql. Below are examples demonstrating both page-based and cursor-based pagination queries along with sample responses.
2.9.1 Basic Pagination
This GraphQL query demonstrates page-based pagination by fetching the first page of book records with a page size of 3. It uses the page and size arguments to specify which page of data to retrieve and how many records each page should contain.
query {
books(page: 0, size: 3) {
content {
id
title
author
}
totalPages
totalElements
currentPage
pageSize
}
}
The response contains a paginated set of books with three entries in the content array, representing the first page of results with books titled “The Great Gatsby,” “1984,” and “To Kill a Mockingbird,” each having their respective IDs and authors. The totalElements field indicates there are 7 books in total in the dataset, while totalPages shows that these are divided into 3 pages based on the page size of 3. The currentPage is 0, confirming this is the first page of results, and pageSize confirms that each page contains 3 books. This structure helps clients understand both the data returned and the overall dataset size for effective navigation through pages.
{
"data": {
"books": {
"content": [
{
"id": "1",
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald"
},
{
"id": "2",
"title": "1984",
"author": "George Orwell"
},
{
"id": "3",
"title": "To Kill a Mockingbird",
"author": "Harper Lee"
}
],
"totalPages": 3,
"totalElements": 7,
"currentPage": 0,
"pageSize": 3
}
}
}
2.9.2 Cursor-Based Pagination (After ID)
This GraphQL query uses cursor-based pagination by specifying an afterId parameter to fetch the next set of books starting after the book with ID 2. The size parameter limits the result to 2 books, allowing clients to efficiently load subsequent data slices without relying on page numbers. This approach is ideal for handling dynamic datasets where record positions might change frequently.
query {
books(afterId: 2, size: 2) {
content {
id
title
author
}
totalPages
totalElements
currentPage
pageSize
}
}
The response returns a list of 2 books in the content array—”To Kill a Mockingbird” with ID 3 and “The Catcher in the Rye” with ID 4—representing the records after the specified cursor. The metadata shows totalElements as 2 (the number of books returned), totalPages as 1 since this is a single slice of data, currentPage as 0 indicating the first result batch, and pageSize as 2 matching the requested size. This structure allows clients to progressively fetch data in a performant and consistent manner.
{
"data": {
"books": {
"content": [
{
"id": "3",
"title": "To Kill a Mockingbird",
"author": "Harper Lee"
},
{
"id": "4",
"title": "The Catcher in the Rye",
"author": "J.D. Salinger"
}
],
"totalPages": 1,
"totalElements": 2,
"currentPage": 0,
"pageSize": 2
}
}
}
3. Conclusion
Pagination is crucial for performance and scalability in GraphQL APIs. Spring Boot makes it seamless to implement both page-based and cursor-based pagination strategies. With just a few classes and a well-defined schema, you can support efficient querying of large datasets.




