Search results
Parsing CSV responses with a custom RestTemplate HttpMessageConverter
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.
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.
Read on to learn:
How to configure RestTemplate to use a connection pool or to troubleshoot RestTemplate’s requests timeout.
How to implement ETags using RestTemplate and Ehcache.
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.
Read on to learn:
How to configure RestTemplate to use a connection pool or to troubleshoot RestTemplate’s requests timeout.
How to implement ETags using RestTemplate and Ehcache.
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: