Comma Separated Values (CSV) is a popular data exchange format frequently used for importing and exporting data between servers and applications.

A CSV file is just a plain-text file that stores data in a tabular format where each row consists of one or more fields and each column represents a specific field. These fields are separated by a delimiter, usually a comma or a tab.

In an earlier article, I wrote about exporting and downloading data as a CSV file in Spring Boot. In this article, you'll learn how to upload and parse a CSV file using Spring Boot & Thymeleaf.

Note: To read and parse a CSV file in core Java, check out reading and parsing a CSV file in Java tutorial.

Dependencies

To upload and parse a CSV file in Spring Boot, you only need the spring-boot-starter-web and opencsv dependencies.

Additionally, we also need spring-boot-starter-thymeleaf for serving Thymeleaf templates. The OpenCSV 3rd-party library will be used for parsing the uploaded CSV file.

Add the following dependencies to your Gradle project's build.gradle file:

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'com.opencsv:opencsv:5.0'

For Maven, include the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.0</version>
</dependency>

To create a new Spring Boot project from scratch, you can either use Spring Initializr or Spring Boot CLI to bootstrap a new application with the above dependencies.

Model Class

OpenCSV allows us to directly map the CSV record fields to a Java object.

Let us create a simple model class named User.java that will be used to populate data from the CSV file:

User.java

public class User {

    @CsvBindByName
    private long id;
    @CsvBindByName
    private String name;
    @CsvBindByName
    private String email;
    @CsvBindByName(column = "country")
    private String countryCode;
    @CsvBindByName
    private int age;

    public User(long id, String name, String email, String countryCode, int age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.countryCode = countryCode;
        this.age = age;
    }
    
    // getters and setters removed for the sake of brevity
 }

As you can see above, we have annotated the User class attributes with the @CsvBindByName annotation.

OpenCSV provides this annotation to specify a binding between a column name of the CSV input and a field in a bean.

You can only use the @CsvBindByName annotation if the CSV file has a header. It accepts up to five parameters like column, required, and locale.

All parameters are options except column. The column paramter is only required if the header column name in the CSV file does not match the bean field name.

Spring Boot Controller

Next, create a Spring Boot controller class named UploadController.java that handles the uploading and parsing of a CSV file:

UploadController.java

@Controller
public class UploadController {

    @GetMapping("/")
    public String index() {
        return "index";
    }

    @PostMapping("/upload-csv-file")
    public String uploadCSVFile(@RequestParam("file") MultipartFile file, Model model) {

        // validate file
        if (file.isEmpty()) {
            model.addAttribute("message", "Please select a CSV file to upload.");
            model.addAttribute("status", false);
        } else {

            // parse CSV file to create a list of `User` objects
            try (Reader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {

                // create csv bean reader
                CsvToBean<User> csvToBean = new CsvToBeanBuilder(reader)
                        .withType(User.class)
                        .withIgnoreLeadingWhiteSpace(true)
                        .build();

                // convert `CsvToBean` object to list of users
                List<User> users = csvToBean.parse();

                // TODO: save users in DB?

                // save users list on model
                model.addAttribute("users", users);
                model.addAttribute("status", true);

            } catch (Exception ex) {
                model.addAttribute("message", "An error occurred while processing the CSV file.");
                model.addAttribute("status", false);
            }
        }

        return "file-upload-status";
    }
}

As you can see above, we have annotated the UploadController class with @Controller to indicate that the annotated class is a web controller.

Each method is decorated with @GetMapping or @PostMapping to bind the path and the HTTP action with that particular method:

  • GET / route renders an HTML form to allow a user to upload a CSV file.
  • POST /upload-csv-file route handles HTTP multipart/form-data requests and accepts a MultipartFile object as a route parameter. This is where we parse the uploaded CSV file into a list of User objects using the CsvToBean class. This method returns an HTML response to either display a list of users or an error message.

Thymeleaf Templates

The next step is to create Thymeleaf templates to allow users to upload a CSV file and display results. To nicely display the HTML form, we will use Bootstrap 4 default styles.

HTML Form for File Upload

Here is a simple HTML form that enables users to select a CSV file for upload:

index.html

<form method="POST" th:action="@{/upload-csv-file}" enctype="multipart/form-data">
    <div class="form-group mt-3">
        <label for="file">Select a CSV file</label>
        <input type="file" name="file" class="form-control-file" id="file" accept=".csv">
    </div>
    <button type="submit" class="btn btn-primary">Import Users</button>
</form>

Display Upload Results

Let us create another Thymeleaf template to display the upload results:

file-upload-status.html

<div class="container py-5">
    <div class="row">
        <div class="col-10 mx-auto">
            <h1>File Upload Status</h1>
            <!--display error if any-->
            <div class="alert alert-danger" role="alert" th:if="${!status}">
                <strong>Error:</strong>
                <span th:text="${message}"></span>
            </div>

            <!--display users list-->
            <table class="table table-striped" th:if="${status}">
                <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">ID</th>
                    <th scope="col">Name</th>
                    <th scope="col">Email</th>
                    <th scope="col">Country</th>
                    <th scope="col">Age</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="user, i : ${users}">
                    <th scope="row" th:text="${i.index + 1}"></th>
                    <td th:text="${user.id}"></td>
                    <td th:text="${user.name}"></td>
                    <td th:text="${user.email}"></td>
                    <td th:text="${user.countryCode}"></td>
                    <td th:text="${user.age}"></td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>

Running & Testing the Application

First of all, you need to create the main application class for the Spring Boot application, as shown below:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Let's run the application by typing the following command in your terminal from the root directory of the project:

$ ./gradlew bootRun

Once the Spring Boot application is started, open http://localhost:8080 in your favorite web browser to see the upload form. Here is what it looks like:

CSV Upload Form

As shown in the above image, select a CSV file and click the "Import Users" button to kickstart the file upload operation.

If everything goes right, you should see a list of users displayed as shown in the following screenshot:

CSV Results

If you forget to select a CSV file or the CSV file is not valid, you should see the following error message displayed on the screen:

CSV Error Message

Finally, here is the sample CSV file I just uploaded in the above example:

users.csv

id,name,email,country,age
100,Atta Shah,atta@example.com,PK,30
101,Alex Jones,alex@example.com,DE,35
102,Jovan Lee,jovan@example.com,FR,25
103,Greg Hover,greg@example.com,US,45

Source Code: Download the complete source code from GitHub available under MIT license.

Conclusion

In this article, you have learned how to upload and parse a CSV file using Spring Boot and Thymeleaf in a web application. We used OpenCSV, a popular open-source library, for parsing the uploaded file into a list of Java objects.

Further Reading

If you like this article, you may be interested in other Spring Boot articles:

✌️ Like this article? Follow me on Twitter and LinkedIn. You can also subscribe to RSS Feed.