Reading CSV file from a servlet inside WAR
last modified July 13, 2020
In this tutorial we read data from a CSV file located in the WEB-INF directory. We use servlets, JSP files, and JSTL library. The web application is deployed on Jetty. The Opencsv library is used to read CSV data.
CSV
CSV (Comma Separated Values) format is a very popular import and export format used in spreadsheets and databases.
In the following web application, we read data from a CSV file inside a WAR file and display the data in a web page. The countries with population larger than a hundred million are marked.
pom.xml
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           ├───bean
│   │           │       Country.java
│   │           ├───service
│   │           │       CountryService.java
│   │           └───web
│   │                   ReadCountries.java
│   ├───resources
│   │       countries.csv
│   └───webapp
│           index.jsp
│           listCountries.jsp
│           showError.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>readcsvfromwar</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>12</maven.compiler.source>
        <maven.compiler.target>12</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>com.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>4.6</version>
        </dependency>
        <dependency>
            <groupId>jstl</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.2.2</version>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.14.v20181114</version>
            </plugin>
        </plugins>
    </build>
</project>
The project uses the following dependencies: avax.servlet-api,
opencsv, and jstl.
Name, Population Slovakia,5429000 Norway,5271000 Croatia,4225000 Russia,143439000 Mexico,122273000 Vietnam,95261000 Sweden,9967000 Iceland,337600 Israel,8622000 Hungary,9830000 Germany,82175700 Japan,126650000
This is the countries.csv file. It is located in the
src/main/resources directory. After building the application, 
the file is copied to the WAR's WEB-INF/classes
directory.
package com.zetcode.bean;
import com.opencsv.bean.CsvBindByName;
import java.util.Objects;
public class Country {
    @CsvBindByName
    private String name;
    @CsvBindByName
    private int population;
    public Country() {
    }
    public Country(String name, int population) {
        this.name = name;
        this.population = 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);
    }
}
This is a Country bean that has two attributes:
name and population.
@CsvBindByName private String name;
The @CsvBindByName maps the name attribute 
to a field in the Name column.
package com.zetcode.service;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.HeaderColumnNameMappingStrategy;
import com.zetcode.bean.Country;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
public class CountryService {
    public static Optional<List<Country>> getListOfCountries() throws IOException {
        List<Country> countries;
        try (InputStream is = CountryService.class.getClassLoader()
                .getResourceAsStream("countries.csv")) {
            if (is == null) {
                return Optional.empty();
            }
            HeaderColumnNameMappingStrategy<Country> strategy
                    = new HeaderColumnNameMappingStrategy<>();
            strategy.setType(Country.class);
            try (var br = new BufferedReader(
                    new InputStreamReader(is, StandardCharsets.UTF_8))) {
                CsvToBean<Country> csvToBean = new CsvToBeanBuilder<Country>(br)
                        .withType(Country.class)
                        .withMappingStrategy(strategy)
                        .withIgnoreLeadingWhiteSpace(true)
                        .build();
                 countries = csvToBean.parse();
            }
        }
        return Optional.of(countries);
    }
}
The CountryService reads data from the CSV file. 
try (InputStream is = CountryService.class.getClassLoader()
    .getResourceAsStream("countries.csv")) {
We get the InputStream to the countries.csv file with the 
getResourceAsStream method.
if (is == null) {
    return Optional.empty();
}
If the input stream was not opened, we return an empty Optional. 
This is used to avoid the null value.
HeaderColumnNameMappingStrategy<Country> strategy
    = new HeaderColumnNameMappingStrategy<>();
strategy.setType(Country.class);
We use the Opencsv's HeaderColumnNameMappingStrategy to 
map Country beans to lines in CSV file. Each line is transformed
into a bean. The mapping is done with the help of the @CsvBindByName
annotations.
try (var br = new BufferedReader(
        new InputStreamReader(is, StandardCharsets.UTF_8))) {
    CsvToBean<Country> csvToBean = new CsvToBeanBuilder<Country>(br)
            .withType(Country.class)
            .withMappingStrategy(strategy)
            .withIgnoreLeadingWhiteSpace(true)
            .build();
     countries = csvToBean.parse();
}
With the CsvToBeanBuilder, we parse the CSV file and transform the 
lines into a list of Country beans.
package com.zetcode.web;
import com.zetcode.bean.Country;
import com.zetcode.service.CountryService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
@WebServlet(name = "ReadCountries", urlPatterns = {"/read"})
public class ReadCountries extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        Optional<List<Country>> countries = CountryService.getListOfCountries();
        String templateName;
        if (countries.isPresent()) {
            request.setAttribute("countries", countries.get());
            templateName = "listCountries.jsp";
        } else {
            templateName = "showError.jsp";
        }
        var dispatcher = request.getRequestDispatcher(templateName);
        dispatcher.forward(request, response);
    }
}
In the ReadCountries servlet, we call the getListOfCountries
service method. If there are some countries, we set the returned list of countries to the 
request object as an attribute. The processing is transferred to the 
listCountries.jsp. If there was no data found, an error message is 
returned.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Countries</title>
    <style>
        .marked { color: chocolate }
    </style>
</head>
<body>
<table>
    <thead>
        <tr>
            <th>Country</th>
            <th>Population</th>
        </tr>
    </thead>
    <tbody>
        <c:forEach items="${countries}" var="count">
            <c:if test="${count.population > 100000000}">
                <tr class="marked">
                    <td>
                        <c:out value="${count.name}"/>
                    </td>
                    <td>
                        <fmt:formatNumber type="number" value="${count.population}" />
                    </td>                   
                </tr>
            </c:if>
            <c:if test="${count.population < 100000000}">
                <tr>
                    <td>
                        <c:out value="${count.name}"/>
                    </td>
                    <td>
                        <fmt:formatNumber type="number" value="${count.population}" />
                    </td>                   
                </tr>
            </c:if>
        </c:forEach>
    </tbody>
    </table>
</body>
</html>
In the listCountries.jsp file, we display the data in an HTML table.
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
We use two JSTL tag libraries: the core and the formatting library.
<c:forEach items="${countries}" var="count">
With the <c:forEach> tag we iterate over the 
countries object.
<c:if test="${count.population > 100000000}">
    <tr class="marked">
        <td>
            <c:out value="${count.name}"/>
        </td>
        <td>
            <fmt:formatNumber type="number" value="${count.population}" />
        </td>                   
    </tr>
</c:if>
If the country's population is larger than one hundred million, we use the marked
class for the row; it displyas the row in a different colour. The test is performed with 
the JSTL's <c:if> tag. The <fmt:formatNumber> tag is used to format
the value.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>List countries</title>
    </head>
    <body>
        <a href="read">List countries</a>
    </body>
</html>
The index.jsp contains a link that calls the ReadCountries
servlet. The servlet reads the data from a CSV file and returns the data in a view
back to the browser.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Error</title>
    </head>
    <body>
        <p>No countries found</p>
    </body>
</html>
This template file shows an error message.
In this tutorial, we have shown how to read CSV data located inside the WAR file.