Pagination is widely used to split a large number of data entries to discrete pages, allowing users to easily toggle through data entries. In this brief tutorial, we'll learn how to implement pagination in a Spring Boot and Thymeleaf based application. We will display a list of customers in Thymeleaf with pagination.

Getting Started

For this tutorial, I assume that you know how to create a Spring Boot project that uses Thymeleaf templating engine. You can also use Spring Initializr to quickly create a Spring Boot project with Thymeleaf and Spring Data JPA dependencies.

Spring Data Models

Our application has only one model (also called domain in Spring Data JPA) named Customer. The model has three attributes: id, name and address:

Customer.java

@Entity
public class Customer {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String address;

    // all-agrument contructor, getters and setters
}

Spring Data Repositories

For our Customer domain, we will create the following repository to load a page of Customers from the database:

CustomerRepository.java

public interface CustomerRepository extends JpaRepository<Customer, Long> {
    Customer findByName(String name);
}

JpaRepository extends PagingAndSortingRepository interface which provides methods to retrieve entities using the pagination and sorting abstraction.

Spring Controller

Let's create a controller which declares /customers end-point to render the customers. The controller method takes optional page and size query parameters to retrieve a list of customers from the database. If no parameters are available, it uses the default values.

CustomerController.java

@Controller
public class CustomerController {
    private CustomerRepository customerRepository;
    
    public CustomerController(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }
    
    @GetMapping("/customers")
    public String customersPage(HttpServletRequest request, Model model) {
        
        int page = 0; //default page number is 0 (yes it is weird)
        int size = 10; //default page size is 10
        
        if (request.getParameter("page") != null && !request.getParameter("page").isEmpty()) {
            page = Integer.parseInt(request.getParameter("page")) - 1;
        }

        if (request.getParameter("size") != null && !request.getParameter("size").isEmpty()) {
            size = Integer.parseInt(request.getParameter("size"));
        }
        
        model.addAttribute("customers", customerRepository.findAll(PageRequest.of(page, size)));
        return "customers";
    }
}

Thymeleaf Template

The final step is to create a Thymeleaf template customers.html which displays the list of customers with pagination based on a model attribute customers from our CustomerController class.

For styling, we are going to use Bootstrap 4 pagination component. It provides utility classes to nicely display the pagination links. We will first iterate over the list to display the customers in a table. Then we will add the pagination right below the table.

customers.html

<table class="table table-bordered">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Address</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="customer : ${customers}">
            <td th:text="${customer.id}"></td>
            <td th:text="${customer.name}"></td>
            <td th:text="${customer.address}"></td>
        </tr>
    </tbody>
</table>

<!-- customers pagination -->
<nav aria-label="Pagination" th:if="${customers.totalPages gt 0}">
    <ul class="pagination justify-content-center font-weight-bold">
        <li class="page-item" th:classappend="${customers.number eq 0} ? 'disabled'">
            <a class="page-link"
               th:href="@{/customers?page={id}(id=${customers.number lt 2 ? 1 : customers.number})}"
               aria-label="Previous" title="Previous Page" data-toggle="tooltip">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        <li class="page-item" th:classappend="${i eq customers.number + 1} ? 'active'"
            th:each="i : ${#numbers.sequence( 1, customers.totalPages, 1)}">
            <a class="page-link" th:href="@{/customers?page={id}(id=${i})}" th:text="${i}"
               th:title="${'Page '+ i}" data-toggle="tooltip"></a>
        </li>
        <li class="page-item" th:classappend="${customers.number + 1 eq customers.totalPages} ? 'disabled'">
            <a class="page-link"
               th:href="@{/customers?page={id}(id=${customers.number + 2})}"
               aria-label="Next" title="Next Page" data-toggle="tooltip">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

When you will click on any page link, it will display the corresponding list of customers with the current page link highlighted.

The above code works great for displaying pagination but there is a problem. It shows all pagination links and if you have lots of customers, these links may be in hundreds or even thousands. It is bad from user experience perspective to display hundreds of page links. One simple solution is to display only 10 next page links (if available) at a time. Here is how we can do this:

<!-- customers pagination -->
<nav aria-label="Pagination" th:if="${customers.totalPages gt 0}">
    <ul class="pagination justify-content-center font-weight-medium">
        <li class="page-item" th:classappend="${customers.number eq 0} ? 'disabled'">
            <a class="page-link svg-icon"
               th:href="@{/admin/customers?page={id}(id=${customers.number lt 2 ? 1 : customers.number})}"
               aria-label="Previous"
               title="Previous Page" rel="tooltip">
                <span aria-hidden="true" data-feather="chevrons-left" width="20" height="20"></span>
            </a>
        </li>
        <li class="page-item" th:classappend="${i eq customers.number + 1} ? 'active'"
            th:each="i : ${#numbers.sequence( customers.number + 1, customers.totalPages > 10 + customers.number ? customers.number + 10 : customers.totalPages, 1)}">
            <a class="page-link" th:href="@{/admin/customers?page={id}(id=${i})}" th:text="${i}"
               th:title="${'Page '+ i}"
               rel="tooltip"></a>
        </li>
        <li class="page-item disabled" th:if="${customers.number + 10 < customers.totalPages}">
            <a class="page-link svg-icon" href="#">
                <span data-feather="more-horizontal" width="20" height="20"></span>
            </a>
        </li>
        <li class="page-item" th:classappend="${customers.number + 1 eq customers.totalPages} ? 'disabled'">
            <a class="page-link svg-icon" th:href="@{/admin/customers?page={id}(id=${customers.number + 2})}"
               aria-label="Next"
               title="Next Page" rel="tooltip">
                <span aria-hidden="true" data-feather="chevrons-right" width="20" height="20"></span>
            </a>
        </li>
    </ul>
</nav>

The above code snippet checks the number of pages left starting from current page. If more than 10 pages are available, it renders only next 10 page links. Otherwise, all next page links are shown. The final result will look like the following:

Spring Boot Pagination Example

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)

Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.