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.