1. OVERVIEW

Even though RestTemplate has been deprecated in favor of WebClient, it’s still a very popular choice to integrate Java applications with in-house or third-party services.

If you find yourself working on application modernization you would most-likely need to integrate with legacy systems. Don’t be surprised if you get HTML, plain text, or CSV responses when integrating with legacy systems.

Parse CSV responses using RestTemplate

Of course you could use RestTemplate to get the response as a String and covert it to a Java object. But that’s not how you do it when retrieving JSON or XML responses.

You would only need:

ResponseEntity<Film> result = this.restTemplate.getForEntity(uri, Film.class);

and RestTemplate’s default HttpMessageConverters take care of the conversion.

This blog post helps you to write a custom RestTemplate HttpMessageConverter to convert CVS responses to Java objects.

2. THE CODE

Let’s split the supporting Java code into subsections to better undertand each part.

2.1. DEPENDENCIES

If you are implementing RESTful applications using Spring Boot, spring-boot-starter-web includes spring-web which includes RestTemplate.

Let’s first bring in opencsv dependency to parse CSV values into POJOs.

pom.xml:

<properties>
  <opencsv.version>5.5.2</opencsv.version>
</properties>

<dependency>
  <groupId>com.opencsv</groupId>
  <artifactId>opencsv</artifactId>
  <version>${opencsv.version}</version>
</dependency>

2.2. OPENCSV PARSER

The RestTemplate instance will be parsing CSV content like:

"Airbus A319neo","31N","A19N"
"Airbus A320","320","A320"
"Airbus A320neo","32N","A20N"
"Airbus A321","321","A321"
"Airbus A321neo","32Q","A21N"

Let’s implement the CSV parser next.

CsvParser.java:

public interface CsvParser<T> {

  List<T> parse(InputStream input) throws IOException;
}

DefaultCsvParser.java:

public class DefaultCsvParser<T> implements CsvParser<T> {

  public DefaultCsvParser(Class<T> clazz) {
    this.clazz = clazz;
  }

  @Override
  public List<T> parse(InputStream input) throws IOException {
    MappingStrategy<T> mappingStrategy = this.getMappingStrategy();

    try (Reader reader = new InputStreamReader(input)) {
      CsvToBean<T> csvToBean = new CsvToBeanBuilder<T>(reader)
        .withType(this.clazz)
        .withMappingStrategy(mappingStrategy)
        .build();
      return csvToBean.parse();
    }
  }

  protected MappingStrategy<T> getMappingStrategy() {
    MappingStrategy<T> result = new ColumnPositionMappingStrategy<>();
    result.setType(this.clazz);
    return result;
  }

// ...
}

This implementation isn’t tied to any Web technology. You could use it in RESTful or Console applications.

The parse() method converts the input to an instance of type this.clazz. The mapping strategy used for this conversion is ColumnPositionMappingStrategy.

"Aerospatiale (Nord) 262","ND2","N262"
"Aerospatiale (Sud Aviation) Se.210 Caravelle","CRV","S210"
"Aerospatiale SN.601 Corvette","NDC","S601"
...
Field Position
Airplane name 0
IATA Code 1
ICAO Code 2


2.3. CSV HttpMessageConverter

Let’s now implement a custom RestTemplate HttpMessageConverter to parse CSV content, CSV-formatted HTTP responses in this case.

CsvHttpMessageConverter.java:

public class CsvHttpMessageConverter<T> extends AbstractGenericHttpMessageConverter<List<T>> {

  private CsvParser<T> csvParser;

  @Override
  protected boolean supports(Class<?> clazz) {
    return List.class.isAssignableFrom(clazz);
  }

  @Override
  public List<T> read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
    throws IOException, HttpMessageNotReadableException {

    return this.csvParser.parse(inputMessage.getBody());
  }

  @Override
  protected List<T> readInternal(Class<? extends List<T>> clazz, HttpInputMessage inputMessage)
    throws IOException, HttpMessageNotReadableException {

    return this.csvParser.parse(inputMessage.getBody());
  }

// ...
}

A CsvHttpMessageConverter initial implementation extended from AbstractHttpMessageConverter, but requests resulted in:

UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [java.util.List<com.asimio.demo.domain.Airplane>]

due to a combination of responseClass being null and there wasn’t found a messageConverter instance of GenericHttpMessageConverter.

CsvHttpMessageConverter needed to extend AbstractGenericHttpMessageConverter to get past this exception.

And all read() and readInternal() do is to execute parse().

Airplane.java:

public class Airplane {

  @CsvBindByPosition(position = 0)
  private String name;

  @CsvBindByPosition(position = 1)
  private String iataCode;

  @CsvBindByPosition(position = 2)
  private String icaoCode;

  // Getters, Setters
}

This is the POJO that is instantiated for each line in the CSV-formatted response. It uses opencsv’s CsvBindByPosition annotation which matches the CSV parser mapping strategy discussed earlier.

2.4. RestTemplate CONFIGURATION

WebClientConfig.java:

@Configuration
public class WebClientConfig {

  @Bean
  public CsvParser<Airplane> csvParser() {
    return new DefaultCsvParser<>(Airplane.class);
  }

  @Bean
  public RestTemplate restTemplate() {
    RestTemplate result = new RestTemplate();
    CsvHttpMessageConverter<Airplane> messageConverter = new CsvHttpMessageConverter<>(
      this.csvParser(),
      MediaType.ALL
    );
    result.getMessageConverters().add(messageConverter);
    return result;
  }

// ...
}

This Spring configuration class instantiates the Airplane CSV parser the CsvHttpMessageConverter uses.

And adds the custom CsvHttpMessageConverter to the default RestTemplate’s message converters list.

2.5. REST CONTROLLER

AirplaneController.java:

@RestController
@RequestMapping(
  value = "/api/airplanes",
  produces = MediaType.APPLICATION_JSON_VALUE
)
public class AirplaneController {

  private final RestTemplate restTemplate;

  @GetMapping(path = "")
  public ResponseEntity<List<Airplane>> findAirplanes() {
    HttpHeaders headers = new HttpHeaders();
    headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));

    HttpEntity<Void> request = new HttpEntity<>(headers);
    ResponseEntity<List<Airplane>> airplanes = this.restTemplate.exchange(
      "https://raw.githubusercontent.com/jpatokal/openflights/master/data/planes.dat",
      HttpMethod.GET,
      request,
      new ParameterizedTypeReference<List<Airplane>>(){}
    );
    // Do something else with airplanes
    return new ResponseEntity<>(airplanes.getBody(), HttpStatus.OK);
  }

// ...
}

A simple RESTful endpoint implementation that retrieves CSV-formatted content from an external source, parses it, and answers back with a JSON-formatted response.

3. SAMPLE REQUEST

curl http://localhost:8080/api/airplanes | json_pp
[
   {
      "iataCode" : "ND2",
      "icaoCode" : "N262",
      "name" : "Aerospatiale (Nord) 262"
   },
   {
      "name" : "Aerospatiale (Sud Aviation) Se.210 Caravelle",
      "icaoCode" : "S210",
      "iataCode" : "CRV"
   },
   {
      "iataCode" : "NDC",
      "name" : "Aerospatiale SN.601 Corvette",
      "icaoCode" : "S601"
   },
...
]

4. CONCLUSION

RestTemplate supports adding or overriding its default list of HttpMessageConverters.

This blog post helped you to implement a custom HttpMessageConverter to parse CSV content.

Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.

5. SOURCE CODE

Accompanying source code for this blog post can be found at: