Affects: Spring Framework 3.2.3

Spring RestTemplate fails giving an answer if the server responds with an illegal HTTP Content-Type like CSV instead of text/csv.

Of course it is bad that the server returns such a content-type, but it would still be good if RestTemplate had a fallback, at least for well-known types like byte[] and String.

We use RestTemplate to load a CSV file from a system with:

restTemplate.getForEntity("http://server/something.csv", byte[].class);

When the server responds with a bad Content-Type like CSV, then:

  • MediaType.parseMediaType() throws an InvalidMediaTypeException
  • HttpMessageConverterExtractor.extractData() doesn't catch it.
  • It seems that null would be a possible fallback, because its only used in logs and canRead() allows it.

Comment From: bclozel

Thanks for the proposal, but I think that such unspecified behavior is hard to implement in Spring Framework as opinions can diverge pretty quickly there. Other applications might already rely on the fact that they're getting exceptions for this behavior and not a fallback that would be surprising for them.

Instead, I would suggest using a custom interceptor for dealing with such misbehaving servers in a way that fits your use case. Something like:

public class MyInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);
        return new ClientHttpResponseWrapper(response);
    }

    static class ClientHttpResponseWrapper implements ClientHttpResponse {

        private final ClientHttpResponse delegate;

        private final HttpHeaders headers;

        public ClientHttpResponseWrapper(ClientHttpResponse delegate) {
            this.delegate = delegate;
            HttpHeaders originalHeaders = new HttpHeaders(delegate.getHeaders());
            if (Objects.equals(originalHeaders.getFirst(HttpHeaders.CONTENT_TYPE), "csv")) {
                originalHeaders.setContentType(MediaType.parseMediaType("text/csv"));
            }
            this.headers = HttpHeaders.readOnlyHttpHeaders(originalHeaders);
        }

        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return this.delegate.getStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return this.delegate.getStatusText();
        }

        @Override
        public void close() {
            this.delegate.close();
        }

        @Override
        public InputStream getBody() throws IOException {
            return this.delegate.getBody();
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.headers;
        }
    }
}