Welcome, in this tutorial, we will understand the sorting in a spring boot application and for this, we will use thymeleaf. 1. Introduction Before going further in this tutorial, we will look at the common terminology such as introduction to Spring Boot, Lombok, Thymeleaf, and Sorting. 1.1 Spring Boot Spring boot is a module that provides rapid application development feature to the spring framework including auto-configuration, standalone-code, and production-ready code It creates applications that are packaged as jar and are directly started using embedded servlet container (such as Tomcat, Jetty or, Undertow). Thus, no need to deploy the war files It simplifies the maven configuration by providing the starter template and helps to resolve the dependency conflicts. It automatically identifies the required dependencies and imports them into the application It helps in removing the boilerplate code, extra annotations, and XML configurations It provides a powerful batch processing and manages the rest endpoints It provides an efficient jpa-starter library to effectively connect the application with the relational databases It offers a Microservice architecture and cloud configuration that manages all the application related configuration properties in a centralized manner 1.2 Lombok Lombok is nothing but a small library which reduces the amount of boilerplate Java code from the project Automatically generates the getters and setters for the object by using the Lombok annotations Hooks in via the Annotation processor API Raw source code is passed to Lombok for code generation before the Java Compiler continues. Thus, produces properly compiled Java code in conjunction with the Java Compiler Under the target/classes folder you can view the compiled class files Can be used with Maven, Gradle IDE, etc. 1.2.1 Lombok features Feature Details val Local variables are declared as final var Mutable local variables @Slf4J Creates an SLF4J logger @Cleanup Will call close() on the resource in the finally block @Getter Creates getter methods for all properties @Setter Creates setter for all non-final properties @EqualsAndHashCode Generates implementations of equals(Object other) and hashCode() By default will use all non-static, non-transient properties Can optionally exclude specific properties @ToString Generates String of class name, and each field separated by commas Optional parameter to include field names Optional parameter to include a call to the super toString method @NoArgsConstructor Generates no-args constructor Will cause compiler error if there are final fields Can optionally force, which will initialize final fields with 0/false/null var - mutable local variables @RequiredArgsContructor Generates a constructor for all fields that are final or marked @NonNull The constructor will throw a NullPointerException if any @NonNull fields are null val - local variables are declared final @AllArgsConstructor Generates a constructor for all properties of the class Any @NotNull properties will have null checks @Data Generates typical boilerplate code for POJOs Combines - @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor No constructor is generated if constructors have been explicitly declared @Builder Implements the Builder pattern for object creation @Value The immutable variant of @Data All fields are made private and final by default 1.3 Thymeleaf Thymeleaf is a server-side java template engine for the web applications It processes the HTML, XML, JS, CSS, and simple text to bring the elegant designing to a web application To use Thymeleaf, you must define the spring-boot-starter-thymeleaf dependency in the pom.xml and mention the xmlns:th="https://thymeleaf.org" library in the templates 1.4 Sorting Sorting is a process of retrieving the data in the ascending or the descending based on a given field To perform pagination and sorting in a spring boot application we will use the PagingAndSortingRepository interface to provide the additional methods to sort the results either in the ascending or the descending order 2. Spring Boot sorting with Thymeleaf Tutorial Here is a systematic guide for implementing this tutorial but before going any further I’m assuming that you are aware of the Spring boot basics. 2.1 Application Pre-requisite To start with this tutorial, we are hoping that you at present have the Lombok plugin installed in the IDE of their favorite choice. If someone needs to go through the Lombok installation on IntelliJ IDE, please watch this video. For installation on Eclipse IDE, please watch this video. 2.2 Tools Used and Project Structure We are using Eclipse Kepler SR2, JDK 8, and Maven. In case you’re confused about where you should create the corresponding files or folder, let us review the project structure of the spring boot application. Fig. 1: Project structure Let us start building the application! 3. Creating a Spring Boot application Below are the steps involved in developing the application. 3.1 Maven Dependency Here, we specify the dependency for the Spring Boot, Spring Data JPA, Thymeleaf, H2 database, Faker, and Lombok. Maven will automatically resolve the other dependencies. The updated file will have the following code. pom.xml 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.springboot.thymeleaf.pagination.sorting</groupId> <artifactId>SpringbootThymeleafPaginationSortingV2</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- embedded database (h2) dependency. --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <!-- lombok dependency. --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- faker dependency to generate some random data. --> <dependency> <groupId>com.github.javafaker</groupId> <artifactId>javafaker</artifactId> <version>1.0.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 3.2 Application Properties Create a new properties file at the location: SpringbootThymeleafPaginationSortingV2/src/main/resources/ and add the following code to it. application.properties 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 server.port=10092 spring.application.name=springboot-thymeleaf-pagination-and-sorting # h2 database settings spring.datasource.username=sa spring.datasource.password= spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver # logging spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.properties.hibernate.show_sql=true # details sql monitoring # logging.level.org.hibernate.SQL=DEBUG # logging.level.org.hibernate.type=TRACE ## browser url for h2 console - http://localhost:10092/h2-console spring.h2.console.enabled=true spring.h2.console.path=/h2-console 3.3 Java Classes Let us write all the java classes involved in this application. 3.3.1 Implementation/Main class Add the following code to the main class to bootstrap the application from the main method. Always remember, the entry point of the spring boot application is the class containing @SpringBootApplication annotation and the static main method. SpringbootThymeleafPaginationSorting.java 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 package com.springboot.thymeleaf.pagination.sorting; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; //Causes Lombok to generate a logger field. @Slf4j //Serves two purposes i.e. configuration and bootstrapping. @SpringBootApplication public class SpringbootThymeleafPaginationSorting { public static void main(String[] args) { SpringApplication.run(SpringbootThymeleafPaginationSorting.class, args); log.info("Springboot pagination and sorting with thymeleaf application is started successfully."); } } 3.3.2 Model class Add the following code to the Employee model class. Employee.java 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.springboot.thymeleaf.pagination.sorting.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; import javax.persistence.*; @Entity @Table(name = "employees") // Causes Lombok to generate toString(), equals(), hashCode(), getter() & setter(), and Required arguments constructor in one go. @Data // Causes Lombok to implement the Builder design pattern for the POJO class. // Usage can be seen in DefaultEmployeesLoader.java -> createNewEmployee() method. @Builder // Causes Lombok to generate a constructor with no parameters. @NoArgsConstructor // Causes Lombok to generate a constructor with 1 parameter for each field in your class. @AllArgsConstructor @Component public class Employee { @Id @GeneratedValue(strategy = GenerationType.AUTO) long id; @Column(name = "first_name", nullable = false) String firstName; @Column(name = "last_name", nullable = false) String lastName; @Column(name = "gender") String gender; @Column(name = "email", nullable = false) String email; @Column(name = "phone_number", unique = true) String phoneNumber; @Column(name = "home_address") String homeAddress; } 3.3.3 Configuration class Add the following code to the bean class that will return the bean object for the faker object. The usage of this object can be seen in the DefaultEmployeesLoader.java class which is used to load the dummy data into the database on the application startup. BeanConfiguration.java 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 package com.springboot.thymeleaf.pagination.sorting.configuration; import com.github.javafaker.Faker; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Locale; @Configuration public class BeanConfiguration { @Bean public Faker faker() { return new Faker(new Locale("en-US")); } } 3.3.4 Data-Access-Object interface Add the following code to the interface that extends the PagingAndSortingRepository interface. EmployeeRepository.java 01 02 03 04 05 06 07 08 09 10 package com.springboot.thymeleaf.pagination.sorting.repository; import com.springboot.thymeleaf.pagination.sorting.model.Employee; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; @Repository public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> { } 3.3.5 Service class Add the following code to the service class where we will call the DAO interface methods to save the data into the database and also fetch the data from the database. EmployeeService.java 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.springboot.thymeleaf.pagination.sorting.service; import com.springboot.thymeleaf.pagination.sorting.model.Employee; import com.springboot.thymeleaf.pagination.sorting.repository.EmployeeRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; //Causes Lombok to generate a logger field. @Slf4j @Service public class EmployeeService { @Autowired private EmployeeRepository repository; public void save(final Employee employee) { repository.save(employee); } public long getTotalEmployees() { log.info("Finding the total count of employees from the dB."); return repository.count(); } public Page<Employee> findPaginated(final int pageNumber, final int pageSize, final String sortField, final String sortDirection) { log.info("Fetching the paginated employees from the dB."); final Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() : Sort.by(sortField).descending(); final Pageable pageable = PageRequest.of(pageNumber - 1, pageSize, sort); return repository.findAll(pageable); } } 3.3.6 Bootstrap class Add the following code to the bootstrap class to save the dummy data into the database on the application startup. This data will be saved in the H2 database. DefaultEmployeesLoader.java 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.springboot.thymeleaf.pagination.sorting.bootstrap; import com.github.javafaker.Faker; import com.springboot.thymeleaf.pagination.sorting.model.Employee; import com.springboot.thymeleaf.pagination.sorting.service.EmployeeService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.util.Random; // Causes Lombok to generate a logger field. @Slf4j // Causes Lombok to generate a constructor with 1 parameter for each field that requires special handling. @RequiredArgsConstructor @Component public class DefaultEmployeesLoader implements CommandLineRunner { private static final String[] GENDER = {"Male", "Female", "Transgender", "Not to specify"}; private static final Random RANDOM = new Random(); private final EmployeeService service; private final Faker faker; @Override public void run(String... args) throws Exception { loadEmployees(); } private void loadEmployees() { int count = 0; if (service.getTotalEmployees() == 0) { for (int x = 0; x < 100; x++) { count = count + 1; service.save(createNewEmployee()); } log.info("Total {} employees are saved in the database.", count); } else { log.info("Default employees are already present in the database."); } } private Employee createNewEmployee() { final String firstName = faker.name().firstName(); final String lastName = faker.name().lastName(); final String gender = GENDER[RANDOM.nextInt(GENDER.length)]; final String emailAddress = firstName.toLowerCase() + "." + lastName.toLowerCase() + "@somecompany.com"; return Employee.builder() .firstName(firstName) .lastName(lastName) .gender(gender) .email(emailAddress) .phoneNumber(faker.phoneNumber().cellPhone()) .homeAddress(faker.address().fullAddress()) .build(); } } 3.3.7 Index Controller class Add the following code to the controller class designed to handle the incoming requests. The class is annotated with the @Controller annotation were the HTTP GET method would return the index page of the application. EmployeeController.java 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.springboot.thymeleaf.pagination.sorting.controller; import com.springboot.thymeleaf.pagination.sorting.model.Employee; import com.springboot.thymeleaf.pagination.sorting.service.EmployeeService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; //Causes Lombok to generate a logger field. @Slf4j @Controller public class EmployeeController { @Autowired private EmployeeService service; // URL - http://localhost:10092/ @GetMapping(value = "/") public String viewIndexPage() { log.info("Redirecting the index page to the controller method for fetching the employees in a " + "paginated fashion."); // During the index page we are using the sort-field as id and sort-dir as asc. return "redirect:page/1?sort-field=id&sort-dir=asc"; } // URL - http://localhost:10092/page/1?sort-field=firstName&sort-dir=desc @GetMapping(value = "/page/{page-number}") public String findPaginated(@PathVariable(name = "page-number") final int pageNo, @RequestParam(name = "sort-field") final String sortField, @RequestParam(name = "sort-dir") final String sortDir, final Model model) { log.info("Getting the employees in a paginated way for page-number = {}, sort-field = {}, and " + "sort-direction = {}.", pageNo, sortField, sortDir); // Hardcoding the page-size to 15. final int pageSize = 15; final Page<Employee> page = service.findPaginated(pageNo, pageSize, sortField, sortDir); final List<Employee> listEmployees = page.getContent(); // Creating the model response. // Note for simplicity purpose we are not making the use of ResponseDto here. // In ideal cases the response will be encapsulated in a class. // pagination parameters model.addAttribute("currentPage", pageNo); model.addAttribute("totalPages", page.getTotalPages()); model.addAttribute("totalItems", page.getTotalElements()); // sorting parameters model.addAttribute("sortField", sortField); model.addAttribute("sortDir", sortDir); model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc"); // employees model.addAttribute("listEmployees", listEmployees); return "index"; } } 4. Thymeleaf Changes We will create a simple HTML page that will display the employees on the browser in smaller chunks (i.e. the paginated approach) and will support the sorting. Create a new HTML file at the location: SpringbootThymeleafPaginationSortingV2/src/main/resources/templates/ and add the following code to it. index.html 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Index page</title> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"> <style type="text/css"> th { text-align: center; font-weight: bold; border-top: none !important; } th, td { white-space: nowrap; } .mt-20 { margin-top: 20px; } </style> </head> <body> <div class="container"> <h3 class="text-info text-center mt-20">Sorting Example : Employees</h3> <!-- employees table --> <table class="table table-striped mt-20 text-center" id="employeesTable"> <thead> <tr> <!-- sorting control via employee id --> <th> <a th:href="@{'/page/' + ${currentPage} + '?sort-field=id&sort-dir=' + ${reverseSortDir}}">Id</a> </th> <!-- sorting control via employee firstname --> <th> <a th:href="@{'/page/' + ${currentPage} + '?sort-field=firstName&sort-dir=' + ${reverseSortDir}}">First name</a> </th> <!-- sorting control via employee lastname --> <th> <a th:href="@{'/page/' + ${currentPage} + '?sort-field=lastName&sort-dir=' + ${reverseSortDir}}">Last name</a> </th> <th>Email address</th> <th>Gender</th> <th>Phone number</th> <th>Home address</th> </tr> </thead> <tbody> <tr th:each="employee : ${listEmployees}"> <td th:text="${employee.id}"></td> <td th:text="${employee.firstName}"></td> <td th:text="${employee.lastName}"></td> <td th:text="${employee.gender}"></td> <td th:text="${employee.email}"></td> <td th:text="${employee.phoneNumber}"></td> <td th:text="${employee.homeAddress}"></td> </tr> </tbody> </table> <!-- pagination control --> <div th:if="${totalPages > 1}"> <div class="row col-sm-10"> <div class="col-sm-2"> Total employees: <strong>[[${totalItems}]]</strong> </div> <div class="col-sm-1"> <span th:each="i: ${#numbers.sequence(1, totalPages)}"> <a th:href="@{'/page/' + ${i} + '?sort-field=' + ${sortField} + '&sort-dir=' + ${sortDir}}" th:if="${currentPage != i}">[[${i}]]</a> <span th:unless="${currentPage != i}">[[${i}]]</span> </span> </div> <div class="col-sm-1"> <a th:href="@{'/page/' + ${currentPage + 1} + '?sort-field=' + ${sortField} + '&sort-dir=' + ${sortDir}}" th:if="${currentPage < totalPages}"> Next </a> <span th:unless="${currentPage < totalPages}">Next</span> </div> <div class="col-sm-1"> <a th:href="@{'/page/' + ${totalPages} + '?sort-field=' + ${sortField} + '&sort-dir=' + ${sortDir}}" th:if="${currentPage < totalPages}"> Last </a> <span th:unless="${currentPage < totalPages}">Last</span> </div> </div> </div> </div> </body> </html> 5. Run the Application To execute the application, right-click on the SpringbootThymeleafPaginationSorting.java class, Run As -> Java Application. Fig. 2: Run the Application 6. Project Demo Open the browser of your choice and hit the following URL. The result will be displayed in a paginated manner (i.e. smaller chunks) and you can click on the page number to retrieve the results as per the page number. 1 http://localhost:10092/ Fig. 3: Index Page You can click on the column names to sort the results based on the sort field either in the ascending or the descending order. Fig. 4: Sorted Result That is all for this tutorial and I hope the article served you whatever you were looking for. Happy Learning and do not forget to share! 7. Summary In this section, you learned: Spring Boot, Thymeleaf, Lombok and it features, and Sorting Sorting implementation in Spring Boot and displaying the elements on the browser using Thymeleaf You can download the sample application as an Eclipse project in the Downloads section. 8. Download the Eclipse Project This was an example of Spring Boot sorting with Thymeleaf. DownloadYou can download the full source code of this example here: Spring Boot sorting with Thymeleaf Tutorial