{"id":1992,"date":"2021-12-15T12:00:00","date_gmt":"2021-12-15T11:00:00","guid":{"rendered":"https:\/\/kindsonthegenius.com\/blog\/how-to-implement-pagination-in-spring-boot-with-thymeleaf\/"},"modified":"2026-08-31T10:26:55","modified_gmt":"2026-08-31T08:26:55","slug":"how-to-implement-pagination-in-spring-boot-with-thymeleaf","status":"publish","type":"post","link":"https:\/\/kindsonthegenius.com\/blog\/how-to-implement-pagination-in-spring-boot-with-thymeleaf\/","title":{"rendered":"Part 1 \u2013 How to Implement Pagination in Spring Boot with Thymeleaf"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><em>Learn how to implement pagination in Spring Boot with Thymeleaf and JpaRepository, display page numbers, and retrieve records one page at a time.<\/em><\/p>\n\n\n<h3>TL;DR<\/h3>\n<ul>\n<li>\n<p>Modify the service to use Spring Data JPA&#8217;s <code>Page<\/code> and <code>Pageable<\/code> objects.<\/p>\n<\/li>\n<li>\n<p>Create a <code>findPage()<\/code> method to retrieve a specific page of records.<\/p>\n<\/li>\n<li>\n<p>Update the controller to pass pagination data to the Thymeleaf template.<\/p>\n<\/li>\n<li>\n<p>Display the current page, total pages, total items, and page numbers.<\/p>\n<\/li>\n<li>\n<p>Add pagination links to the Countries table using Thymeleaf.<\/p>\n<\/li>\n<li>\n<p>The next part adds First, Previous, Next, and Last navigation links.<\/p>\n<\/li>\n<\/ul>\n<p>In this tutorial, you will learn how to easily implement pagination in Spring Boot with Thymeleaf page and JpaRepository. This would be a step-by-step tutorial with all the code snippets you need.<\/p>\n<p>I assume you already know how to get list of items from a repository and display them on a page using Thymeleaf.\u00a0 You can review this here.<\/p>\n<p>In this tutorial, we would adjust the Countries page to include pagination<\/p>\n<p>The following is covered:<\/p>\n<ol>\n<li><a href=\"#t1\">What we Currently Have<\/a><\/li>\n<li><a href=\"#t2\">Modify the Service<\/a><\/li>\n<li><a href=\"#t3\">Write the GetOnePage Method<\/a><\/li>\n<li><a href=\"#t4\">Write the GetFirstPage Method<\/a><\/li>\n<li><a href=\"#t5\">Next Steps<\/a><\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<h4><strong id=\"t1\">1. What we Currently Have<\/strong><\/h4>\n<p>This is what is going to change<\/p>\n<p><strong>CountryService.java<\/strong> \u2013 this class has the findAll() method as shown below:<\/p>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #008800; font-weight: bold;\">public<\/span> List<span style=\"color: #333333;\">&lt;<\/span>Country<span style=\"color: #333333;\">&gt;<\/span> <span style=\"color: #0066bb; font-weight: bold;\">findAll<\/span><span style=\"color: #333333;\">(){<\/span>\n    <span style=\"color: #008800; font-weight: bold;\">return<\/span> countryRepository<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">findAll<\/span><span style=\"color: #333333;\">();<\/span>\n<span style=\"color: #333333;\">}<\/span>\n<\/pre>\n<p>we would modify this<\/p>\n<p><strong>CountryController.java<\/strong> \u2013 we would modify this as well<\/p>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #555555; font-weight: bold;\">@GetMapping<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"\/parameters\/countries\"<\/span><span style=\"color: #333333;\">)<\/span>\n<span style=\"color: #008800; font-weight: bold;\">public<\/span> String  <span style=\"color: #0066bb; font-weight: bold;\">getAll<\/span><span style=\"color: #333333;\">(<\/span>Model model<span style=\"color: #333333;\">,<\/span> String keyword<span style=\"color: #333333;\">){<\/span>\n    List<span style=\"color: #333333;\">&lt;<\/span>Country<span style=\"color: #333333;\">&gt;<\/span> countries<span style=\"color: #333333;\">;<\/span>\n    countries <span style=\"color: #333333;\">=<\/span> keyword <span style=\"color: #333333;\">==<\/span> <span style=\"color: #008800; font-weight: bold;\">null<\/span><span style=\"color: #333333;\">?<\/span> countryService<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">findAll<\/span><span style=\"color: #333333;\">():<\/span>countryService<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">findByKeyword<\/span><span style=\"color: #333333;\">(<\/span>keyword<span style=\"color: #333333;\">);<\/span>\n    model<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">addAttribute<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"countries\"<\/span><span style=\"color: #333333;\">,<\/span> countries<span style=\"color: #333333;\">);<\/span>\n    <span style=\"color: #008800; font-weight: bold;\">return<\/span> <span style=\"background-color: #fff0f0;\">\"\/parameters\/countries\"<\/span><span style=\"color: #333333;\">;<\/span>\n<span style=\"color: #333333;\">}<\/span>\n<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>countries.html<\/strong> \u2013 this file contains the table markup. We would simply adjust the markup to include a footer in the table with pagination links.<\/p>\n<p>&nbsp;<\/p>\n<h4><strong id=\"t2\">2. Modify the Service<\/strong><\/h4>\n<p>The changes we would make here is quite simple.<\/p>\n<p>We would write a new method called findPage(int pageNumber). This method takes an int parameter which would be that page to return. The return type is Page&lt;&gt; and not List&lt;&gt;. Then using this page number, we would create a Pageable object. Finally, we just call the repository findAll() method and pass it the Pageable object we created.<\/p>\n<p>Note that a Page&lt;&gt; object contains a number of useful information including totalPages, content (list of items) and totalElements.<\/p>\n<p>The complete method is given below:<\/p>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #008800; font-weight: bold;\">public<\/span> Page<span style=\"color: #333333;\">&lt;<\/span>Country<span style=\"color: #333333;\">&gt;<\/span> <span style=\"color: #0066bb; font-weight: bold;\">findPage<\/span><span style=\"color: #333333;\">(<\/span><span style=\"color: #333399; font-weight: bold;\">int<\/span> pageNumber<span style=\"color: #333333;\">){<\/span>\n    Pageable pageable <span style=\"color: #333333;\">=<\/span> PageRequest<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">of<\/span><span style=\"color: #333333;\">(<\/span>pageNumber <span style=\"color: #333333;\">-<\/span> <span style=\"color: #0000dd; font-weight: bold;\">1<\/span><span style=\"color: #333333;\">,<\/span><span style=\"color: #0000dd; font-weight: bold;\">5<\/span><span style=\"color: #333333;\">);<\/span>\n    <span style=\"color: #008800; font-weight: bold;\">return<\/span> countryRepository<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">findAll<\/span><span style=\"color: #333333;\">(<\/span>pageable<span style=\"color: #333333;\">);<\/span>\n<span style=\"color: #333333;\">}<\/span>\n<\/pre>\n<p>Here, the PageRequest.of() takes the pageNumber-1 (because the page number is zero-based) as first argument. The second argument is the size. Also notice that the findAll() method can also take a Pageable as argument.<\/p>\n<p>&nbsp;<\/p>\n<h4><strong id=\"t3\">3. Modify the Controller \u2013 GetOnePage() Method<\/strong><\/h4>\n<p>So here, instead of findAll(), we would get one page at a time.<\/p>\n<p>Now we need to write a new method that takes the pageNumber as second parameter. Remember the first parameter is the Model object. Then, we call the findPage() method, giving it the pageNumber\/currentPage as argument.<\/p>\n<p>The pageNumber would come as a PathVariable.<\/p>\n<p>Then once we have the page, we would extract the following:<\/p>\n<ul>\n<li>Total Pages \u2013 total pages in available<\/li>\n<li>Total Elements \u2013 total elements in the page (it\u2019s possible the last page would have less elements than other pages)<\/li>\n<li>Content \u2013 the actual list of items. In this case, it\u2019s list of Countries<\/li>\n<\/ul>\n<p>Then we send all of this together with the currentPage to the UI as model attributes.<\/p>\n<p>Finally as before, we return the countries template page.<\/p>\n<p>The complete method is given below:<\/p>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #555555; font-weight: bold;\">@GetMapping<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"\/parameters\/countries\/page\/{pageNumber}\"<\/span><span style=\"color: #333333;\">)<\/span>\n<span style=\"color: #008800; font-weight: bold;\">public<\/span> String <span style=\"color: #0066bb; font-weight: bold;\">getOnePage<\/span><span style=\"color: #333333;\">(<\/span>Model model<span style=\"color: #333333;\">,<\/span> <span style=\"color: #555555; font-weight: bold;\">@PathVariable<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"pageNumber\"<\/span><span style=\"color: #333333;\">)<\/span> <span style=\"color: #333399; font-weight: bold;\">int<\/span> currentPage<span style=\"color: #333333;\">){<\/span>\n    Page<span style=\"color: #333333;\">&lt;<\/span>Country<span style=\"color: #333333;\">&gt;<\/span> page <span style=\"color: #333333;\">=<\/span> countryService<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">findPage<\/span><span style=\"color: #333333;\">(<\/span>currentPage<span style=\"color: #333333;\">);<\/span>\n    <span style=\"color: #333399; font-weight: bold;\">int<\/span> totalPages <span style=\"color: #333333;\">=<\/span> page<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">getTotalPages<\/span><span style=\"color: #333333;\">();<\/span>\n    <span style=\"color: #333399; font-weight: bold;\">long<\/span> totalItems <span style=\"color: #333333;\">=<\/span> page<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">getTotalElements<\/span><span style=\"color: #333333;\">();<\/span>\n    List<span style=\"color: #333333;\">&lt;<\/span>Country<span style=\"color: #333333;\">&gt;<\/span> countries <span style=\"color: #333333;\">=<\/span> page<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">getContent<\/span><span style=\"color: #333333;\">();<\/span>\n\n    model<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">addAttribute<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"currentPage\"<\/span><span style=\"color: #333333;\">,<\/span> currentPage<span style=\"color: #333333;\">);<\/span>\n    model<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">addAttribute<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"totalPages\"<\/span><span style=\"color: #333333;\">,<\/span> totalPages<span style=\"color: #333333;\">);<\/span>\n    model<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">addAttribute<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"totalItems\"<\/span><span style=\"color: #333333;\">,<\/span> totalItems<span style=\"color: #333333;\">);<\/span>\n    model<span style=\"color: #333333;\">.<\/span><span style=\"color: #0000cc;\">addAttribute<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"countries\"<\/span><span style=\"color: #333333;\">,<\/span> countries<span style=\"color: #333333;\">);<\/span>\n\n    <span style=\"color: #008800; font-weight: bold;\">return<\/span> <span style=\"background-color: #fff0f0;\">\"\/parameters\/countries\"<\/span><span style=\"color: #333333;\">;<\/span>\n<span style=\"color: #333333;\">}<\/span>\n<\/pre>\n<p>&nbsp;<\/p>\n<h4><strong id=\"t4\">4. Modify the Controller \u2013 GetFirstPage() Method<\/strong><\/h4>\n<p>This is the method that would be called when the page loads for the first time.<\/p>\n<p>So basically, this method would simply call the getOnePage() method which we already have. The page number argument would be 1.<\/p>\n<p>The complete method is given below:<\/p>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #555555; font-weight: bold;\">@GetMapping<\/span><span style=\"color: #333333;\">(<\/span><span style=\"background-color: #fff0f0;\">\"\/parameters\/countries\"<\/span><span style=\"color: #333333;\">)<\/span>\n<span style=\"color: #008800; font-weight: bold;\">public<\/span> String <span style=\"color: #0066bb; font-weight: bold;\">getAllPages<\/span><span style=\"color: #333333;\">(<\/span>Model model<span style=\"color: #333333;\">){<\/span>\n    <span style=\"color: #008800; font-weight: bold;\">return<\/span> <span style=\"color: #0066bb; font-weight: bold;\">getOnePage<\/span><span style=\"color: #333333;\">(<\/span>model<span style=\"color: #333333;\">,<\/span> <span style=\"color: #0000dd; font-weight: bold;\">1<\/span><span style=\"color: #333333;\">);<\/span>\n<span style=\"color: #333333;\">}<\/span>\n<\/pre>\n<p>&nbsp;<\/p>\n<h4><strong>4. Add Pagination Footer to HTML Template<\/strong><\/h4>\n<p>In the footer, we would like to have three features<\/p>\n<ul>\n<li>a text like: Page 1 of 5<\/li>\n<li>page numbers as links<\/li>\n<\/ul>\n<p>The code below is added as a footer to the Countries table in the HTML page<\/p>\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #007700;\">&lt;footer<\/span> <span style=\"color: #0000cc;\">style=<\/span><span style=\"background-color: #fff0f0;\">\"font-size: large\"<\/span> <span style=\"color: #0000cc;\">class=<\/span><span style=\"background-color: #fff0f0;\">\"panel-footer\"<\/span><span style=\"color: #007700;\">&gt;<\/span>\n    Total Items [[${totalItems}]] : Page [[${currentPage}]] of [[${totalPages}]]\n    <span style=\"color: #880000; font-weight: bold;\">&amp;nbsp;<\/span> <span style=\"color: #880000; font-weight: bold;\">&amp;nbsp;<\/span> - <span style=\"color: #880000; font-weight: bold;\">&amp;nbsp;<\/span>\n    <span style=\"color: #007700;\">&lt;span<\/span> <span style=\"color: #0000cc;\">th:each=<\/span><span style=\"background-color: #fff0f0;\">\"i: ${#numbers.sequence(1, totalPages)}\"<\/span><span style=\"color: #007700;\">&gt;<\/span>\n        <span style=\"color: #007700;\">&lt;a<\/span> <span style=\"color: #0000cc;\">th:href=<\/span><span style=\"background-color: #fff0f0;\">\"@{'\/parameters\/countries\/page\/' + ${i}}\"<\/span><span style=\"color: #007700;\">&gt;<\/span>[[${i}]]<span style=\"color: #007700;\">&lt;\/a&gt;<\/span>\n        <span style=\"color: #880000; font-weight: bold;\">&amp;nbsp;<\/span> <span style=\"color: #880000; font-weight: bold;\">&amp;nbsp;<\/span>\n    <span style=\"color: #007700;\">&lt;\/span&gt;<\/span>\n<span style=\"color: #007700;\">&lt;\/footer&gt;<\/span>\n<\/pre>\n<p>You can now test to make sure everything works well!<\/p>\n<p>But we are not done yet<\/p>\n<p>&nbsp;<\/p>\n<h4><strong id=\"t5\">5. Next Steps<\/strong><\/h4>\n<p>If you have used pagination feature before, you will see that there are alway four additional buttons:<\/p>\n<p><strong>First, Next, Previous and Last<\/strong><\/p>\n<p>We would implement this in the next part.<\/p>\n<p>Then you\u2019ll also notice that when we are in a current page, the page number is still hyperlinked. This has to be fixed. When in current page, then the page number should not be a link.<\/p>\n<p>Let\u2019s move on to Part 2. By the way, I recommend you watch the video tutorial if it\u2019s clearer for you. Available in <a href=\"https:\/\/www.youtube.com\/c\/KindsonTheTechPro\" target=\"_blank\" rel=\"noopener\">my YouTube Channel (Kindson The Tech Pro).<\/a><\/p>\n<h3>FAQs<\/h3>\n<p><strong>How do I implement pagination in Spring Boot with Thymeleaf?<\/strong><\/p>\n<p>Use Spring Data JPA&#8217;s <code>Page<\/code> and <code>Pageable<\/code> objects to retrieve a specific number of records at a time, then pass the pagination information to the Thymeleaf template.<\/p>\n<p><strong>What is the purpose of the <code>Pageable<\/code> object in Spring Boot pagination?<\/strong><\/p>\n<p><code>Pageable<\/code> specifies which page to retrieve and how many records should be included on that page. In the tutorial, <code>PageRequest.of(pageNumber - 1, 5)<\/code> retrieves five records per page.<\/p>\n<p><strong>What information does the Spring Data <code>Page<\/code> object provide?<\/strong><\/p>\n<p>The <code>Page<\/code> object provides useful information such as the total number of pages, total number of elements, and the actual content returned for the current page.<\/p>\n<p><strong>Why does the pagination code use <code>pageNumber - 1<\/code>?<\/strong><\/p>\n<p>Spring Data JPA uses zero-based page numbering, while the tutorial displays pages starting from 1. Therefore, one is subtracted from the page number when creating the <code>PageRequest<\/code>.<\/p>\n<p><strong>How do I display page numbers in Thymeleaf?<\/strong><\/p>\n<p>You can use Thymeleaf&#8217;s <code>#numbers.sequence()<\/code> to generate a sequence from 1 to the total number of pages and create a link for each page.<\/p>\n<h3>Final Thoughts<\/h3>\n<p>Pagination is useful when a table contains many records because it allows the application to retrieve and display a manageable number of records at a time. With Spring Boot, Spring Data JPA&#8217;s <code>Page<\/code> and <code>Pageable<\/code> make it straightforward to implement this functionality.<\/p>\n<p>In this tutorial, we modified the service and controller to retrieve individual pages and passed the pagination details to the Thymeleaf template. The next step is to improve the navigation by adding First, Previous, Next, and Last buttons and handling the current page appropriately.<\/p>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to implement pagination in Spring Boot with Thymeleaf and JpaRepository, display page numbers, and retrieve records one page at a time.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"categories":[414],"tags":[],"class_list":["post-1992","post","type-post","status-publish","format-standard","hentry","category-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/1992","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/comments?post=1992"}],"version-history":[{"count":2,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/1992\/revisions"}],"predecessor-version":[{"id":2594,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/1992\/revisions\/2594"}],"wp:attachment":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/media?parent=1992"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/categories?post=1992"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/tags?post=1992"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}