Pagination is widely used to split large data entries into 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 you know how to create a Spring Boot project that uses the 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-argument constructor, 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);
}

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

Spring Controller

Let's create a controller which declares the /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 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 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 large number of customers, these links may be in hundreds or even thousands.

From the user experience perspective, It is inadequate to display hundreds of page links. One simple solution is only displaying 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 the current page. If more than 10 pages are available, it renders only the 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 me on Twitter and LinkedIn. You can also subscribe to RSS Feed.