ZetCode

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.

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

resources/countries.csv
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.

com/zetcode/bean/Country.java
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.

com/zetcode/CountryService.java
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.

com/zetcode/web/ReadCountries.java
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.

webapp/listCountries.jsp
<%@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.

webapp/index.jsp
<%@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.

webapp/showError.jsp
<%@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.