57

Using spring, with this code :

List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for(HttpMessageConverter httpMessageConverter : messageConverters){
  System.out.println(httpMessageConverter);
}
ResponseEntity<ProductList> productList = restTemplate.getForEntity(productDataUrl,ProductList.class);

I get

org.springframework.http.converter.ByteArrayHttpMessageConverter@34649ee4
org.springframework.http.converter.StringHttpMessageConverter@39fba59b
org.springframework.http.converter.ResourceHttpMessageConverter@383580da
org.springframework.http.converter.xml.SourceHttpMessageConverter@409e850a
org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter@673074aa
org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter@1e3b79d3
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@52bb1b26

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.mycopmany.ProductList] and content type [text/html;charset=UTF-8]

The a snippet of the pojo :

@XmlRootElement(name="TheProductList")
public class ProductList {

@XmlElement(required = true, name = "date")
private LocalDate importDate;
Koray Tugay
  • 22,894
  • 45
  • 188
  • 319
NimChimpsky
  • 46,453
  • 60
  • 198
  • 311
  • The media type coming back is `text/html` and not `application/xml`. Have you looked at the response to see if you are getting and HTML page for an error message rather than the real XML response you are looking for? – bdoughan Feb 18 '14 at 13:52
  • 1
    @BlaiseDoughan thx. No its the data, with an incorrect header. From a third party ... – NimChimpsky Feb 18 '14 at 14:02
  • so I am downloading the response to a file, converting to a streamsource, and using the unmarshaller that way. – NimChimpsky Feb 18 '14 at 14:39
  • For consuming text/html respone in spring rest template, look at this answer and upvote if it works for you! https://stackoverflow.com/a/76557476/2181576 – veritas Jun 26 '23 at 14:13

12 Answers12

49

From a Spring point of view, none of the HttpMessageConverter instances registered with the RestTemplate can convert text/html content to a ProductList object. The method of interest is HttpMessageConverter#canRead(Class, MediaType). The implementation for all of the above returns false, including Jaxb2RootElementHttpMessageConverter.

Since no HttpMessageConverter can read your HTTP response, processing fails with an exception.

If you can control the server response, modify it to set the Content-type to application/xml, text/xml, or something matching application/*+xml.

If you don't control the server response, you'll need to write and register your own HttpMessageConverter (which can extend the Spring classes, see AbstractXmlHttpMessageConverter and its sub classes) that can read and convert text/html.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
24

You could also simply tell your RestTemplate to accept all media types:

@Bean
public RestTemplate restTemplate() {
   final RestTemplate restTemplate = new RestTemplate();

   List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
   MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
   converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
   messageConverters.add(converter);
   restTemplate.setMessageConverters(messageConverters);

   return restTemplate;
}
Markoorn
  • 2,477
  • 1
  • 16
  • 31
  • Not working for response File and type application/actet-stream for me: HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.io.File` out of START_ARRAY token – Hollerweger Oct 21 '20 at 15:25
  • Giving `400 Bad Request: [{"error":"invalid_request"}` error – Suresh May 24 '22 at 09:48
15

If you are using Spring Boot, you might want to make sure you have the Jackson dependency in your classpath. You can do this manually via:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

Or you can use the web starter:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
Wim Deblauwe
  • 25,113
  • 20
  • 133
  • 211
12

In addition to all the answers, if you happen to receive in response text/html while you've expected something else (i.e. application/json), it may suggest that an error occurred on the server side (say 404) and the error page was returned instead of your data.

So it happened in my case. Hope it will save somebody's time.

user1913596
  • 469
  • 6
  • 16
  • 1
    Change your ResponseEntity type to String. Print out the response. Will help you debug easily !! Eg: `ResponseEntity responseEntity= restTemplate.exchange(uri, HttpMethod.POST, requestEntity, String.class);` – wardaddy Jun 15 '22 at 03:58
9

If you can't change server media-type response, you can extend GsonHttpMessageConverter to process additional support types

public class MyGsonHttpMessageConverter extends GsonHttpMessageConverter {
    public MyGsonHttpMessageConverter() {
        List<MediaType> types = Arrays.asList(
                new MediaType("text", "html", DEFAULT_CHARSET),
                new MediaType("application", "json", DEFAULT_CHARSET),
                new MediaType("application", "*+json", DEFAULT_CHARSET)
        );
        super.setSupportedMediaTypes(types);
    }
}
Vadim Zin4uk
  • 1,716
  • 22
  • 18
4

You can make up a class, RestTemplateXML, which extends RestTemplate. Then override doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor<T>), and explicitly get response-headers and set content-type to application/xml.

Now Spring reads the headers and knows that it is `application/xml'. It is kind of a hack but it works.

public class RestTemplateXML extends RestTemplate {

  @Override
  protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
        ResponseExtractor<T> responseExtractor) throws RestClientException {

     logger.info( RestTemplateXML.class.getSuperclass().getSimpleName() + ".doExecute() is overridden");

     Assert.notNull(url, "'url' must not be null");
     Assert.notNull(method, "'method' must not be null");
     ClientHttpResponse response = null;
     try {
        ClientHttpRequest request = createRequest(url, method);
        if (requestCallback != null) {
           requestCallback.doWithRequest(request);
        }
        response = request.execute();

        // Set ContentType to XML
        response.getHeaders().setContentType(MediaType.APPLICATION_XML);

        if (!getErrorHandler().hasError(response)) {
           logResponseStatus(method, url, response);
        }
        else {
           handleResponseError(method, url, response);
        }
        if (responseExtractor != null) {
           return responseExtractor.extractData(response);
        }
        else {
           return null;
        }
     }
     catch (IOException ex) {
        throw new ResourceAccessException("I/O error on " + method.name() +
              " request for \"" + url + "\":" + ex.getMessage(), ex);
     }
     finally {
        if (response != null) {
           response.close();
        }
     }

  }

  private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
     if (logger.isDebugEnabled()) {
        try {
           logger.debug(method.name() + " request for \"" + url + "\" resulted in " +
                 response.getRawStatusCode() + " (" + response.getStatusText() + ")");
        }
        catch (IOException e) {
           // ignore
        }
     }
  }

  private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
     if (logger.isWarnEnabled()) {
        try {
           logger.warn(method.name() + " request for \"" + url + "\" resulted in " +
                 response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
        }
        catch (IOException e) {
           // ignore
        }
     }
     getErrorHandler().handleError(response);
  }
}
4

Try this:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>
Stéphane GRILLON
  • 11,140
  • 10
  • 85
  • 154
Leonardozm
  • 65
  • 1
  • 5
    Welcome to Stack Overflow! While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – Scott Weldon Jul 29 '16 at 17:21
0

Or you can use

public void setSupportedMediaTypes(List supportedMediaTypes)

method which belongs to AbstractHttpMessageConverter<T>, to add some ContentTypes you like. This way can let the MappingJackson2HttpMessageConverter canRead() your response, and transform it to your desired Class, which on this case,is ProductList Class.

and I think this step should hooked up with the Spring Context initializing. for example, by using

implements ApplicationListener { ... }

avidya
  • 91
  • 6
0

This is not answering the problem but if anyone comes to this question when they stumble upon this exception of no suitable message converter found, here is my problem and solution.

In Spring 4.0.9, we were able to send this

    JSONObject jsonCredential = new JSONObject();
    jsonCredential.put(APPLICATION_CREDENTIALS, data);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

ResponseEntity<String> res = restTemplate.exchange(myRestUrl), HttpMethod.POST,request, String.class);

In Spring 4.3.5 release, we starting seeing errors with the message that converter was not found.

The way Convertors work is that if you have it in your classpath, they get registered.

Jackson-asl was still in classpath but was not being recognized by spring. We replaced Jackson-asl with faster-xml jackson core.

Once we added I could see the converter being registered.

enter image description here

Mhluzi Bhaka
  • 1,364
  • 3
  • 19
  • 42
vsingh
  • 6,365
  • 3
  • 53
  • 57
0

A refinement of Vadim Zin4uk's answer is just to use the existing GsonHttpMessageConverter class but invoke the setSupportedMediaTypes() setter.

For spring boot apps, this results into adding to following to your configuration classes:

@Bean
public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
    GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
    converter.setGson(gson);
    List<MediaType> supportedMediaTypes = converter.getSupportedMediaTypes();
    if (! supportedMediaTypes.contains(TEXT_PLAIN)) {
        supportedMediaTypes = new ArrayList<>(supportedMediaTypes);
        supportedMediaTypes.add(TEXT_PLAIN);
        converter.setSupportedMediaTypes(supportedMediaTypes);
    }
    return converter;
}
Guillaume Berche
  • 3,049
  • 2
  • 17
  • 18
0

I also had the same error message : "Could not extract response: no suitable HttpMessageConverter found for response type ..."

This occured when I was trying to get info from a link that did not return the object type I wanted to convert or when the link did not return anything. I handled it using a try catch block :

 try {
        status = restTemplate
            .getForObject(statusResourceUrl, Status.class);

        //TODO add new exceptions if necessary or simply use Exception
    } catch (BeanCreationException | UnknownContentTypeException | HttpClientErrorException e) {
        status.setStatus("DOWN");
        System.out.println("exception " + e.getMessage());
    }
0

I was also facing the same issue in last week. Tried the above solution which marked as accepted but didnt worked.

When this will come : While calling the external URL(REST call) and the response is the complex object.

What was wrong : was adding unnecessary the converter using the below code

org.springframework.web.client.restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));

Solution: Just add the below dependency

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

Because spring boot add all the default message converters. No need to add any extra. Not even any JaxB dependency. If present please delete and Try it will work

Danke!!

rahulnikhare
  • 1,362
  • 1
  • 18
  • 25