Java Servlet pagination
last modified August 24, 2023
Java servlet pagination tutorial shows how to do pagination using Java servlets. In the example, Bootstrap is used for the UI.
Pagination
Pagination is the process of dividing content into several pages. The user has a navigation interface for accessing these pages with specific page links. The navigation often includes previous/next and first/last links. Pagination is used when there is lots of data in the database or there are many comments to be shown in one page.
Java Servlet
Servlet is a Java class which responds to a particular type of network request - most commonly an HTTP request. Java servlets are used to create web applications. They run in servlet containers such as Tomcat or Jetty. Modern-day Java web development uses frameworks that are built on top of servlets.
Bootstrap
Bootstrap is a UI library from Twitter for creating responsive, mobile-first web applications.
Java Servlet pagination example
In the following web application, we load data from MySQL database and display it in the table. There is a navigation system to go over all data from the database table. Before the data is displayed in the table, the user has an option to choose how many rows the table will display. The web application is deployed on Jetty server.
In addition to fetching data from the database table, we also need to know the number of all rows in the database table, the number of records per page, and the number of pages to display in the navigation. The number of all rows in the database is figured out by an SQL statement. The number of records per page is selected by user in an HTML form. Finally, the number of pages in the pagination is computed from the other two values.
CREATE TABLE countries(id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), population INT); INSERT INTO countries(name, population) VALUES('China', 1382050000); INSERT INTO countries(name, population) VALUES('India', 1313210000); INSERT INTO countries(name, population) VALUES('USA', 324666000); INSERT INTO countries(name, population) VALUES('Indonesia', 260581000); INSERT INTO countries(name, population) VALUES('Brazil', 207221000); INSERT INTO countries(name, population) VALUES('Pakistan', 196626000); INSERT INTO countries(name, population) VALUES('Nigeria', 186988000); INSERT INTO countries(name, population) VALUES('Bangladesh', 162099000); INSERT INTO countries(name, population) VALUES('Nigeria', 186988000); INSERT INTO countries(name, population) VALUES('Russia', 146838000); INSERT INTO countries(name, population) VALUES('Japan', 126830000); INSERT INTO countries(name, population) VALUES('Mexico', 122273000); INSERT INTO countries(name, population) VALUES('Philippines', 103738000); INSERT INTO countries(name, population) VALUES('Ethiopia', 101853000); INSERT INTO countries(name, population) VALUES('Vietnam', 92700000); INSERT INTO countries(name, population) VALUES('Egypt', 92641000); INSERT INTO countries(name, population) VALUES('Germany', 82800000); INSERT INTO countries(name, population) VALUES('the Congo', 82243000); INSERT INTO countries(name, population) VALUES('Iran', 82800000); INSERT INTO countries(name, population) VALUES('Turkey', 79814000); INSERT INTO countries(name, population) VALUES('Thailand', 68147000); INSERT INTO countries(name, population) VALUES('France', 66984000); INSERT INTO countries(name, population) VALUES('United Kingdom', 60589000); INSERT INTO countries(name, population) VALUES('South Africa', 55908000); INSERT INTO countries(name, population) VALUES('Myanmar', 51446000); INSERT INTO countries(name, population) VALUES('South Korea', 68147000); INSERT INTO countries(name, population) VALUES('Colombia', 49129000); INSERT INTO countries(name, population) VALUES('Kenya', 47251000); INSERT INTO countries(name, population) VALUES('Spain', 46812000); INSERT INTO countries(name, population) VALUES('Argentina', 43850000); INSERT INTO countries(name, population) VALUES('Ukraine', 42603000); INSERT INTO countries(name, population) VALUES('Sudan', 41176000); INSERT INTO countries(name, population) VALUES('Algeria', 40400000); INSERT INTO countries(name, population) VALUES('Poland', 38439000);
This SQL script creates the countries
table in MySQL.
pom.xml src ├── main │ ├── java │ │ └── com │ │ └── zetcode │ │ ├── model │ │ │ └── Country.java │ │ ├── service │ │ │ ├── CountryService.java │ │ │ └── ICountryService.java │ │ └── web │ │ └── ReadCountries.java │ ├── resources │ └── webapp │ ├── index.html │ └── listCountries.jsp └── test └── java
This is the project structure.
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>PaginationEx</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>13</maven.compiler.source> <maven.compiler.target>13</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.0</version> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.30.v20200611</version> </plugin> </plugins> </build> </project>
This is the Maven POM file. The javax.servlet-api
artifact is
for servlets. The spring-jdbc
dependency is used for the JdbcTemplate library,
which simplifies database programming in Java. The mysql-connector-java
is a MySQL driver for Java language. The jstl
dependency provides
some additional functionality to JSP pages.
The maven-war-plugin
is responsible for collecting all artifact dependencies,
classes and resources of the web application and packaging them into a web application archive (WAR).
The jetty-maven-plugin
is a useful Maven plugin for rapid development
and testing. It creates a web application, starts a Jetty web server, and deploys
the application on the server.
package com.zetcode.model; import java.util.Objects; public class Country { private String name; private int population; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPopulation() { return population; } public void setPopulation(int population) { this.population = population; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Country country = (Country) o; return population == country.population && Objects.equals(name, country.name); } @Override public int hashCode() { return Objects.hash(name, population); } }
The Country
bean holds one row from the
countries
database table.
package com.zetcode.web; import com.zetcode.model.Country; import com.zetcode.service.CountryService; import java.io.IOException; import java.util.List; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name = "ReadCountries", urlPatterns = {"/ReadCountries"}) public class ReadCountries extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); int currentPage = Integer.parseInt(request.getParameter("currentPage")); int recordsPerPage = Integer.parseInt(request.getParameter("recordsPerPage")); var countryService = new CountryService(); List<Country> countries = countryService.findCountries(currentPage, recordsPerPage); request.setAttribute("countries", countries); int rows = countryService.getNumberOfRows(); int nOfPages = rows / recordsPerPage; if (nOfPages % recordsPerPage > 0) { nOfPages++; } request.setAttribute("noOfPages", nOfPages); request.setAttribute("currentPage", currentPage); request.setAttribute("recordsPerPage", recordsPerPage); RequestDispatcher dispatcher = request.getRequestDispatcher("listCountries.jsp"); dispatcher.forward(request, response); } }
The ReadCountries
servlet determines how much data will be
retrieved from the request attributes and reads the specified amount of rows
from the database table.
@WebServlet(name = "ReadCountries", urlPatterns = {"/ReadCountries"})
The Java class is decorated with the @WebServlet
annotation. It is mapped
to the ReadCountries
URL pattern.
response.setContentType("text/html;charset=UTF-8");
The servlet will output data in HTML and the encoding of the data is set to UTF-8.
int currentPage = Integer.parseInt(request.getParameter("currentPage")); int recordsPerPage = Integer.parseInt(request.getParameter("recordsPerPage"));
From the request we get two important values: the current page and the number of records per page. (The validation of the values is skipped.)
var countryService = new CountryService(); List<Country> countries = countryService.findCountries(currentPage, recordsPerPage); request.setAttribute("countries", countries);
CountryService
is a service class for connecting to the database
and reading data. The list of countries is retrieved and set as an attribute to
the request. It will be used later by the target JSP page.
int rows = countryService.getNumberOfRows(); int nOfPages = rows / recordsPerPage; if (nOfPages % recordsPerPage > 0) { nOfPages++; }
We get the number of all rows from the database table with the
getNumberOfRows
service method. We calculate the number of pages in
the navigation.
request.setAttribute("noOfPages", nOfPages); request.setAttribute("currentPage", currentPage); request.setAttribute("recordsPerPage", recordsPerPage);
The number of pages, the current page, and the number of records per page are values that we need to build the pagination.
var dispatcher = request.getRequestDispatcher("listCountries.jsp"); dispatcher.forward(request, response);
The processing is forwarded to the listCountries.jsp
page.
package com.zetcode.service; import com.zetcode.model.Country; import java.util.List; public interface ICountryService { List<Country> findCountries(int currentPage, int numOfRecords); Integer getNumberOfRows(); }
The ICountryService
contains two contract methods: findCountries
and getNumberOfRows
.
package com.zetcode.service; import com.zetcode.model.Country; import java.sql.SQLException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.SimpleDriverDataSource; public class CountryService implements ICountryService { @Override public List<Country> findCountries(int currentPage, int recordsPerPage) { List<Country> countries = null; int start = currentPage * recordsPerPage - recordsPerPage; try { String sql = "SELECT * FROM countries LIMIT ?, ?"; var ds = new SimpleDriverDataSource(); ds.setDriver(new com.mysql.jdbc.Driver()); ds.setUrl("jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC"); ds.setUsername("user7"); ds.setPassword("7user"); var jtm = new JdbcTemplate(ds); countries = jtm.query(sql, new Object[] {start, recordsPerPage}, new BeanPropertyRowMapper<>(Country.class)); } catch (SQLException ex) { Logger.getLogger(CountryService.class.getName()).log(Level.SEVERE, null, ex); } return countries; } @Override public Integer getNumberOfRows() { Integer numOfRows = 0; try { String sql = "SELECT COUNT(id) FROM countries"; var ds = new SimpleDriverDataSource(); ds.setDriver(new com.mysql.jdbc.Driver()); ds.setUrl("jdbc:mysql://localhost:3306/testdb?serverTimezone=UTC"); ds.setUsername("user7"); ds.setPassword("7user"); var jtm = new JdbcTemplate(ds); numOfRows = jtm.queryForObject(sql, Integer.class); } catch (SQLException ex) { Logger.getLogger(CountryService.class.getName()).log(Level.SEVERE, null, ex); } return numOfRows; } }
The CountryService
contains the implementation of the two contract methods.
String sql = "SELECT * FROM countries LIMIT ?, ?";
The SQL LIMIT clause is used to fetch the amount of rows for the current page.
var jtm = new JdbcTemplate(ds); countries = jtm.query(sql, new Object[] {start, recordsPerPage}, new BeanPropertyRowMapper(Country.class));
JdbcTemplate
is used to execute the SQL statement. The rows are automatically
mapped to the Country
bean with the help of the BeanPropertyRowMapper
.
String sql = "SELECT COUNT(id) FROM countries";
With this SQL statement, we get the number of rows from the database table.
<!DOCTYPE html> <html> <head> <title>Home page</title> <meta charset="UTF-8"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> </head> <body> <main class="m-3"> <h1>Show countries</h1> <form action="ReadCountries"> <input type="hidden" name="currentPage" value="1"> <div class="form-group col-md-4"> <label for="records">Select records per page:</label> <select class="form-control" id="records" name="recordsPerPage"> <option value="5">5</option> <option value="10" selected>10</option> <option value="15">15</option> </select> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </main> </body> </html>
This is the home page. It contains an HTML form to select the number of records per page
with the select
tag. The form uses style classes from the Bootstrap library.
Upon submitting the form, the processing is sent to the ReadCountries
servlet.
<input type="hidden" name="currentPage" value="1">
The form contains a hidden input
tag which sets the currentPage
parameter to 1.
<select class="form-control" id="records" name="recordsPerPage"> <option value="5">5</option> <option value="10" selected>10</option> <option value="15">15</option> </select>
The select
tags allows to choose 5, 10, or 15 records per page.
<button type="submit" class="btn btn-primary">Submit</button>
The Submit button executes the form.
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Countries</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> </head> <body> <main class="m-3"> <div class="row col-md-6"> <table class="table table-striped table-bordered table-sm"> <tr> <th>Name</th> <th>Population</th> </tr> <c:forEach items="${countries}" var="country"> <tr> <td>${country.getName()}</td> <td>${country.getPopulation()}</td> </tr> </c:forEach> </table> </div> <nav aria-label="Navigation for countries"> <ul class="pagination"> <c:if test="${currentPage != 1}"> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${currentPage-1}">Previous</a> </li> </c:if> <c:forEach begin="1" end="${noOfPages}" var="i"> <c:choose> <c:when test="${currentPage eq i}"> <li class="page-item active"><a class="page-link"> ${i} <span class="sr-only">(current)</span></a> </li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${i}">${i}</a> </li> </c:otherwise> </c:choose> </c:forEach> <c:if test="${currentPage lt noOfPages}"> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${currentPage+1}">Next</a> </li> </c:if> </ul> </nav> </main> </body> </html>
The listCountries.jsp
displays the data in the table and the
pagination system. The Bootstrap is used to make the UI responsive and look
good.
<table class="table table-striped table-bordered table-sm">
The table
, table-striped
, table-bordered
,
table-sm
are all Bootstrap classes.
<c:forEach items="${countries}" var="country"> <tr> <td>${country.getName()}</td> <td>${country.getPopulation()}</td> </tr> </c:forEach>
With the JSTL's forEach
tag, we display all data for the current
page.
<c:if test="${currentPage != 1}"> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${currentPage-1}">Previous</a> </li> </c:if>
With the c:if
tag we only show the Previous link when there is one. In the link, we
pass the recordsPerPage
and currentPage
values to the request object.
<c:forEach begin="1" end="${noOfPages}" var="i"> <c:choose> <c:when test="${currentPage eq i}"> <li class="page-item active"><a class="page-link"> ${i} <span class="sr-only">(current)</span></a> </li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${i}">${i}</a> </li> </c:otherwise> </c:choose> </c:forEach>
With the forEach
tag, we display all the page links.
$ mvn jetty:run
We run the Jetty server and navigate to localhost:8080
.
The example shows a table filled with data and the pagination system. The currently selected page is highlighted.
In this article we have shown how to create a pagination system in a web application with a Java Servlet.