Java Servlet pagination

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 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.

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.

countries_mysql.sql
CREATE TABLE Countries(ID BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, 
    Name VARCHAR(100), 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.

$ tree
.
├── nb-configuration.xml
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           ├── bean
    │   │           │   └── Country.java
    │   │           ├── service
    │   │           │   ├── CountryService.java
    │   │           │   └── ICountryService.java
    │   │           └── web
    │   │               └── ReadCountries.java
    │   ├── resources
    │   └── webapp
    │       ├── index.html
    │       ├── listCountries.jsp
    │       ├── META-INF
    │       │   └── context.xml
    │       └── WEB-INF
    └── test
        └── java

This is the project structure.

pom.xml
<?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>JavaServletPagination</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>JavaServletPagination</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</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>2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </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 theFI 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).

context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/JavaServletPagination"/>

In the Tomcat context.xml file, we define the context path. It is the name of the web application.

Country.java
package com.zetcode.bean;

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;
    }
}

The Country bean holds one row from the Countries database table.

ReadCountries.java
package com.zetcode.web;

import com.zetcode.bean.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.valueOf(request.getParameter("currentPage"));
        int recordsPerPage = Integer.valueOf(request.getParameter("recordsPerPage"));
        
        CountryService 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.valueOf(request.getParameter("currentPage"));
int recordsPerPage = Integer.valueOf(request.getParameter("recordsPerPage"));

From the request we get two important values: the current page and the number of records per page.

CountryService 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.

RequestDispatcher dispatcher = request.getRequestDispatcher("listCountries.jsp");
dispatcher.forward(request, response);

The processing is forwarded to the listCountries.jsp page.

ICountryService.java
package com.zetcode.service;

import com.zetcode.bean.Country;
import java.util.List;

public interface ICountryService  {
    
    public List<Country> findCountries(int currentPage, int numOfRecords);
    public int getNumberOfRows();
}

The ICountryService contains two contract methods: findCountries() and getNumberOfRows().

CountryService.java
package com.zetcode.service;

import com.zetcode.bean.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 ?, ?";
            
            SimpleDriverDataSource ds = new SimpleDriverDataSource();
            ds.setDriver(new com.mysql.jdbc.Driver());
            ds.setUrl("jdbc:mysql://localhost:3306/testdb");
            ds.setUsername("testuser");
            ds.setPassword("test623");
            
            JdbcTemplate 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 int getNumberOfRows() {
        
        int numOfRows = 0;
        
        try {
            String sql = "SELECT COUNT(Id) FROM Countries";
            
            SimpleDriverDataSource ds = new SimpleDriverDataSource();
            ds.setDriver(new com.mysql.jdbc.Driver());
            ds.setUrl("jdbc:mysql://localhost:3306/testdb");
            ds.setUsername("testuser");
            ds.setPassword("test623");
            
            JdbcTemplate 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.

JdbcTemplate 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.

index.html
<!DOCTYPE html>
<html>
<head>
    <title>Home page</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">        
</head>
    
<body 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>

<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" ></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" ></script>

</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.

listCountries.jsp
<%@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://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
</head>

<body 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}&currentPage=${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}&currentPage=${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}&currentPage=${currentPage+1}">Next</a>
            </li>
        </c:if>              
    </ul>
</nav>

<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>

</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}&currentPage=${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}&currentPage=${i}">${i}</a>
            </li>
        </c:otherwise>
    </c:choose>
</c:forEach>

With the forEach tag, we display all the page links.

Java Servlet Pagination
Figure: Java Servlet Pagination

The example shows a table filled with data and the pagination system. The currently selected page is highlighted.

In this tutorial, we have shown how to create a pagination system in a web application with a Java Servlet.

You might also be interested in the following related tutorials: Java servlet upload file, Java Log4j tutorial, Java Servlet RESTful client, Java RequestDispatcher, Serving plain text from Java servlet, Java servlet image tutorial, or Java tutorial