Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

CSV is one of the common data exchange formats between systems and applications. A common use case is building Java applications that deal with these CSV files. While writing data to a CSV file, we need to map our Plain Old Java Object ( POJO ) to a CSV format.

In this tutorial, we’ll learn how to map POJO to CSV format with a custom position and header name.

2. OpenCSV Library

OpenCSV is a very popular library for working with CSV files. We first need to add a Maven dependency to our project:

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.9</version>
</dependency>

3. Generating Input Records

For this article, we’ll generate sample input records that we will map to CSV records.

3.1. Application Record

Application record is a simple POJO that contains id, name, age, and created_at fields:

public record Application(String id, String name, Integer age, String created_at) {}

3.2. List of Applications

We’ll generate a list of applications that will be later converted to CSV format:

List<Application> applications = List.of(
  new Application("123", "Sam", 34, "2023-08-11"),
  new Application("456", "Tam", 44, "2023-02-11"),
  new Application("890", "Jam", 54, "2023-03-11")
);

4. POJO to CSV

The default mapping of POJO to CSV is straightforward. We can use StatefulBeanToCsvBuilder with a defined separator and other configs, and then we can write it with FilerWriter:

public static void beanToCSVWithDefault(List<Application> applications) throws Exception {
    try (FileWriter writer = new FileWriter("application.csv")) {
      var builder = new StatefulBeanToCsvBuilder<Application>(writer)
        .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
        .withSeparator(',')
        .build();
      
      builder.write(applications);
    }
}

The default mapping converts POJO to CSV with an ascending field name, but it’s not helpful if we want the header in a custom format.

4.1. Custom Header Strategy

If we want our output CSV file with a custom header, we can define and use a custom header strategy when writing to a CSV file.

For example, if we want our header with the lowercase name, we can override generateHeader:

public class CustomCSVWriterStrategy<T> extends HeaderColumnNameMappingStrategy<T> {
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        String[] header = super.generateHeader(bean);
        return Arrays.stream(header)
          .map(String::toLowerCase)
          .toArray(String[]::new);
    }
}

Once we have a custom header mapping strategy, we can build StatefulBeanToCsvBuilder and then write POJO to CSV file in CSV format:

public static void beanToCSVWithCustomHeaderStrategy(List<Application> applications)
  throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {
    try (FileWriter writer = new FileWriter("application.csv")){
      var mappingStrategy = new CustomCSVWriterStrategy<Application>();
      mappingStrategy.setType(Application.class);

      var builder = new StatefulBeanToCsvBuilder<Application>(writer)
        .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
        .withMappingStrategy(mappingStrategy)
        .build();
      builder.write(applications);
    }
}

4.2. Custom Position Strategy

Writing a CSV file with a CSV field having a certain defined position is also possible. All we’ve to do is annotate the application record property with the required field position:

public record Application(
    @CsvBindByPosition(position = 0)
    String id,
    @CsvBindByPosition(position = 1)
    String name,
    @CsvBindByPosition(position = 2)
    Integer age,
    @CsvBindByPosition(position = 3)
    String created_at) {}

Now, we can write POJO to CSV format using annotated Application records:

public static void beanToCSVWithCustomPositionStrategy(List<Application> applications) throws Exception {
    try (FileWriter writer = new FileWriter("application3.csv")) {
      var builder = new StatefulBeanToCsvBuilder<Application1>(writer)
        .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
        .build();
     
      builder.write(applications);
   }
}

4.3. Custom Position Strategy and Header Name

If we want the CSV field along with the header at a certain position, we can do that using the custom column positioning strategy.

First, we’ll need to annotate the application record class properties with position and header name:

public record Application1(
    @CsvBindByName(column = "id", required = true)
    @CsvBindByPosition(position = 1)
    String id,
    @CsvBindByName(column = "name", required = true)
    @CsvBindByPosition(position = 0)
    String name,
    @CsvBindByName(column = "age", required = true)
    @CsvBindByPosition(position = 2)
    Integer age,
    @CsvBindByName(column = "position", required = true)
    @CsvBindByPosition(position = 3)
    String created_at) {}

Once the record class is ready, we need to write a custom column positioning strategy.

This strategy will include logic to generate header and field positions:

public class CustomColumnPositionStrategy<T> extends ColumnPositionMappingStrategy<T> {
    @Override
    public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
        super.generateHeader(bean);
        return super.getColumnMapping();
    }
}

Now that we’re done with the record and column positioning strategy, we’re ready to use them inside client logic and generate a CSV file:

public static void beanToCSVWithCustomHeaderAndPositionStrategy(List<Application1> applications)
  throws IOException, CsvRequiredFieldEmptyException, CsvDataTypeMismatchException {
    try (FileWriter writer = new FileWriter("application4.csv")){
        var mappingStrategy = new CustomColumnPositionStrategy<Application1>();
        mappingStrategy.setType(Application1.class);

        var builder = new StatefulBeanToCsvBuilder<Application1>(writer)
          .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
          .withMappingStrategy(mappingStrategy)
          .build();
        builder.write(applications);
    }
}

5. Conclusion

In this tutorial, we learned how to convert POJO to CSV format and write to the CSV file. We discussed and wrote examples for including the header along with CSV fields. Additionally, we wrote custom header and positioning strategies as well.

OpenCSV doesn’t provide an out-of-the-box solution for having custom positioning and header for CSV files, but it’s easy to write a custom strategy to include a field and header at the required position.

As always, the example code is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.