Upload CSV Files in Spring Boot REST API with Kotlin and OpenCSV

In this tutorial, we will learn how to upload CSV files in Spring Boot REST API with Kotlin programming language and OpenCSV library.
A featured image for category: Spring

1. Introduction

A Comma Separated Values (CSV) files are delimited text files that use a comma to separate values (mostly, but other characters might be used as well). In such files, each line is a data record.

You might ask yourself, why should you even care about such files? Well, the answer is quite simple: these kinds of files are often used for exchanging the data between different systems. You can be sure, that most of the real-life applications, will require you to process CSV files.

In this pretty short tutorial, we will learn how to upload CSV files in Spring Boot REST API with Kotlin programming language and OpenCSV library.

2. Prepare Test Data

Before we will jump to the Spring Boot project, let’s prepare the test data. I’ve already prepared them for you, so you can just download this CSV file. Below, you can see the structure of the data:

The image presents the screenshot of the test data

Please notice, that this file matches the model of the data we will be using in our application (but I highly recommend for you to experiment a little and prepare your own data).

3. Imports

With that being done, we can switch to our Spring Boot project and add the required dependencies:

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.opencsv:opencsv:5.2")

 

Image shows two ebooks people can get for free after joining newsletter

 

4. Prepare Models

4.1. Create a User Class

As the first step, let’s prepare the User class structure, which will represent the row of the CSV file:

data class User(
    var id: Long? = null,
    var firstName: String? = null,
    var lastName: String? = null,
    var email: String? = null,
    var phoneNumber: String? = null,
    var age: Int? = null
)

4.2. Create Custom Exceptions

This step of the tutorial is not necessary. However, I wanted to share a pretty cool Spring feature, which allows us to mark the exception class with the status code we want to return. Let’s implement two classes for later:

@ResponseStatus(HttpStatus.BAD_REQUEST)
class BadRequestException(msg: String) : RuntimeException(msg)

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
class CsvImportException(msg: String) : RuntimeException(msg)

As you probably guessed, the BadRequestException will return the 400 Bad Request status code to the user, while the CsvImportException will return the 500 Internal Server Error.

5. Implement CSV Service

As the next step, let’s implement the CSV service. Let’s start by adding a validator function:

@Service
class CsvService {
    private fun throwIfFileEmpty(file: MultipartFile) {
        if (file.isEmpty)
            throw BadRequestException("Empty file")
    }
}

This simple function will throw the BadRequestException if the file has no content or no file has been chosen in the multipart form.

Nextly, let’s add the createCSVToBean function:

private fun createCSVToBean(fileReader: BufferedReader?): CsvToBean<User> =
    CsvToBeanBuilder<User>(fileReader)
        .withType(User::class.java)
        .withIgnoreLeadingWhiteSpace(true)
        .build()

We used here the CsvToBeanBuilder class to configure the CsvToBean which will be used to convert the CSV data to objects. If you would like to know, what else can be configured with it, I highly recommend checking it’s documentation here.

The last helper function we will create is the closeFileReader:

private fun closeFileReader(fileReader: BufferedReader?) {
    try {
        fileReader!!.close()
    } catch (ex: IOException) {
        throw CsvImportException("Error during csv import")
    }
}

All it does is closing the stream and releasing any system resources associated with it.

Finally, we can combine together all of the above and create the uploadCsvFile function:

fun uploadCsvFile(file: MultipartFile): List {
    throwIfFileEmpty(file)
    var fileReader: BufferedReader? = null

    try {
        fileReader = BufferedReader(InputStreamReader(file.inputStream))
        val csvToBean = createCSVToBean(fileReader)

        return csvToBean.parse()
    } catch (ex: Exception) {
        throw CsvImportException("Error during csv import")
    } finally {
        closeFileReader(fileReader)
    }
}

6. Configure CSV and Bean Binding

Before we will be able to parse our CSV input file, we need to specify how do we want the data to be bound to our bean. We can do that in several ways, but in this tutorial, I will show you the two most frequently used.

6.1. Bind With @CsvBindByName

This annotation specifies a binding between a column name of the CSV input and a field in a bean.

data class User(
    @CsvBindByName(column = "Id")
    var id: Long? = null,
    @CsvBindByName(column = "First Name")
    var firstName: String? = null,
    @CsvBindByName(column = "Last Name")
    var lastName: String? = null,
    @CsvBindByName(column = "Email")
    var email: String? = null,
    @CsvBindByName(column = "Phone number")
    var phoneNumber: String? = null,
    @CsvBindByName(column = "Age")
    var age: Int? = null
)

Please notice here, that the name of the column must be identical to the name of the field.

6.2. Bind With @CsvBindByPosition

Another way is to bind the data based on a column number of the CSV input:

data class UserWithCsvBindByPosition(
    @CsvBindByPosition(position = 0)
    var id: Long? = null,
    @CsvBindByPosition(position = 1)
    var firstName: String? = null,
    @CsvBindByPosition(position = 2)
    var lastName: String? = null,
    @CsvBindByPosition(position = 3)
    var email: String? = null,
    @CsvBindByPosition(position = 4)
    var phoneNumber: String? = null,
    @CsvBindByPosition(position = 5)
    var age: Int? = null
)

But that’s not all we need to do here. The first line in our CSV file contains headers. In this scenario, we have to configure our CsvToBean to skip it by adding the withSkipLines invocation:

private fun createCSVToBean(fileReader: BufferedReader?): CsvToBean =
    CsvToBeanBuilder(fileReader)
        .withType(User::class.java)
        .withSkipLines(1)
        .withIgnoreLeadingWhiteSpace(true)
        .build()

7. Create REST Controller

As the last step, we need to configure the REST endpoint. Let’s create the CsvController and add the uploadCsvFile function to it:

@RestController
@RequestMapping("/api/csv")
class CsvController(
    private val csvService: CsvService
) {
    @PostMapping
    fun uploadCsvFile(
        @RequestParam("file") file: MultipartFile
    ): ResponseEntity<List> {
        val importedEntries = csvService.uploadCsvFile(file)

        return ResponseEntity.ok(importedEntries)
    }
}

A MultipartFile parameter is a representation of an uploaded file received in a multipart request.

8. Validation

Finally, we can check if everything works fine. Let’s figure out, how we can do that with Postman or curl command.

8.1. Testing Using Postman

All we need to do in Postman is to specify the URL, select the form-data as the request body type, and add our file with the appropriate key:
The image shows the screenshot from Postman application testing.

8.2. POSTing CSV File with cURL

If we would like to test with the command line, let’s specify the following:

curl -X POST -F 'file=@users.csv' http://localhost:8080/api/csv

8. Summary

And that would be all for today. This time, we’ve learned how to upload CSV files in Spring Boot REST API with Kotlin programming language and OpenCSV library.

I hope you will find this article useful. If you would like to share some thoughts on that with me or ask additional questions, I encourage you to do so by our fan page, group, or contact form.

For the source code, please visit this GitHub project.

Share this:

Related content

Newsletter
Image presents 3 ebooks with Java, Spring and Kotlin interview questions.

Never miss any important updates from the Kotlin world and get 3 ebooks!

You may opt out any time. Terms of Use and Privacy Policy