resttemplate getForObject map responsetype
Asked Answered
R

7

36

Update 02/05/2018 (about 4 years later)...I tested this again as people have been upvoting my question/answer and Sotirios Delimanolis is correct that I should not have to write the code in my answer to make this work. I used basically the same RestTemplate/REST service setup as shown in my question with the REST service having a confirmed response content type of application/json and RestTemplate was able to process the response with no issues into a Map.


I'm invoking a rest service that returns JSON like this:

{
   "some.key" : "some value",
   "another.key" : "another value"
}

I would like to think that I can invoke this service with a java.util.Map as the response type but that's not working for me. I get this exception:

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map]

Should I just specify String as the response type and convert the JSON to a Map?

Edit I

Here's my restTemplate call:

private Map<String, String> getBuildInfo(String buildUrl) {
    return restTemplate.getForObject(buildUrl, Map.class);
}

Here's how I'm setting up the restTemplate:

@PostConstruct
public void initialize() {
    List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
    interceptors.add(new ClientHttpRequestInterceptor() {
        @Override
        public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
            HttpRequestWrapper requestWrapper = new HttpRequestWrapper(request);
            requestWrapper.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
            return execution.execute(requestWrapper, body);
        }
    });
    restTemplate.setInterceptors(interceptors);
}

Edit II

Full error message:

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [application/octet-stream]
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:108) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:549) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:502) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:239) ~[spring-web-4.0.3.RELEASE.jar:4.0.3.RELEASE]
    at idexx.ordering.services.AwsServersServiceImpl.getBuildInfo(AwsServersServiceImpl.java:96) ~[classes/:na]
Rhyner answered 13/6, 2014 at 15:21 Comment(6)
Is it valid to have a dot in the key name? I would think that would be somewhat confusing.Olodort
@EngineerDollery It's JSON, it's fine.Springtail
Can you show us how you create and use your RestTemplate?Springtail
Also, you haven't posted the full error message. Please do that.Springtail
@SotiriosDelimanolis -- it's JSON, so it's valid, but it's still not fine. If you want to convert this to a javascript object and then use dot notation to access the object you'd be in a bit of a pickle.Olodort
@EngineerDollery I'm pretty sure Javascript is smart enough to handle this. In any case, the error message is unrelated to the value of the keys.Springtail
S
16

As I had previously noted, your error message is showing us that you are receiving application/octet-stream as a Content-Type.

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [application/octet-stream]

As such, Jackson's MappingJackson2HttpMessageConverter cannot parse the content (it's expecting application/json).


Original answer:

Assuming your HTTP response's Content-Type is application/json and you have have Jackson 1 or 2 on the classpath, a RestTemplate can deserialize JSON like you have into a java.util.Map just fine.

With the error you are getting, which you haven't shown in full, either you've registered custom HttpMessageConverter objects which overwrite the defaults ones, or you don't have Jackson on your classpath and the MappingJackson2HttpMessageConverter isn't registered (which would do the deserialization) or you aren't receiving application/json.

Springtail answered 13/6, 2014 at 15:49 Comment(6)
@ZackMacomber Please post the full stacktrace and full error message.Springtail
oops - forgot to post it...putting it up there now.Rhyner
@ZackMacomber As you can see from the error message, the response doesn't have its content-type set to application/json. That's what you need to change. If your API doesn't return it, inject it like you do for the request.Springtail
Just to clarify, this means you can do this: ResponseEntity<? extends HashMap<String,Object>> responseEntity = restTemplate.getForEntity(restEndPoint, (Class<? extends HashMap<String,Object>>)HashMap.class)Engud
Also, one more thing to check, because I was about to lose it, is if it's not working for a junit test, run through any special config you have for the junit context as well, if it's different from the primary config. ughEngud
In case it helps anyone, the exact Jackson 2 dependency you need is com.fasterxml.jackson.core:jackson-databind. I don't know the coordinates for Jackson 1.Berm
T
44

RestTemplate has a method named exchange that takes an instance of ParameterizedTypeReference as parameter.

To make a GET request that returns a java.util.Map, just create an instance of an anonym class that inherits from ParameterizedTypeReference.

ParameterizedTypeReference<Map<String, String>> responseType =
        new ParameterizedTypeReference<>() {};

You can then invoke the exchange method:

RequestEntity<Void> request = RequestEntity.get("http://example.com/foo")
                 .accept(MediaType.APPLICATION_JSON).build();
Map<String, String> jsonDictionary = restTemplate.exchange(request, responseType).getBody();
Tricia answered 4/4, 2017 at 10:7 Comment(4)
This won't change anything. The HTTP response has a content type that the RestTemplate doesn't know how to process. The RestTemplate will never get to the point in handling where it can use your ParameterizedTypeReference.Springtail
why it has been up voted by people. I can't see it working?Canzonet
It works fine. exchange method is able to handle ParameterizedTypeReference.Staal
Checked here, still works fine with Map<String, Object>Zippy
S
16

As I had previously noted, your error message is showing us that you are receiving application/octet-stream as a Content-Type.

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [application/octet-stream]

As such, Jackson's MappingJackson2HttpMessageConverter cannot parse the content (it's expecting application/json).


Original answer:

Assuming your HTTP response's Content-Type is application/json and you have have Jackson 1 or 2 on the classpath, a RestTemplate can deserialize JSON like you have into a java.util.Map just fine.

With the error you are getting, which you haven't shown in full, either you've registered custom HttpMessageConverter objects which overwrite the defaults ones, or you don't have Jackson on your classpath and the MappingJackson2HttpMessageConverter isn't registered (which would do the deserialization) or you aren't receiving application/json.

Springtail answered 13/6, 2014 at 15:49 Comment(6)
@ZackMacomber Please post the full stacktrace and full error message.Springtail
oops - forgot to post it...putting it up there now.Rhyner
@ZackMacomber As you can see from the error message, the response doesn't have its content-type set to application/json. That's what you need to change. If your API doesn't return it, inject it like you do for the request.Springtail
Just to clarify, this means you can do this: ResponseEntity<? extends HashMap<String,Object>> responseEntity = restTemplate.getForEntity(restEndPoint, (Class<? extends HashMap<String,Object>>)HashMap.class)Engud
Also, one more thing to check, because I was about to lose it, is if it's not working for a junit test, run through any special config you have for the junit context as well, if it's different from the primary config. ughEngud
In case it helps anyone, the exact Jackson 2 dependency you need is com.fasterxml.jackson.core:jackson-databind. I don't know the coordinates for Jackson 1.Berm
S
14

I think you can achieve what you're aiming for simply using the RestTemplate and specifying a JsonNode as the response type.

    ResponseEntity<JsonNode> response = 
         restTemplate.exchange(url, HttpMethod.GET, entity, JsonNode.class);

    JsonNode map = response.getBody();

    String someValue = map.get("someValue").asText();
Shanda answered 2/8, 2017 at 19:50 Comment(2)
Next, you'll want this: Faster XML Jackson: Remove double quotesContractile
This is pretty much the best way to do it, with the exception that you need to add the accept: application/json header and it's all good to go!Chagrin
R
9

Update 02/05/2018 (about 4 years later)...I tested this again as people have been upvoting my question/answer and Sotirios Delimanolis is correct that I should not have to write the code in my answer to make this work. I used basically the same RestTemplate/REST service setup as shown in my question with the REST service having a confirmed response content type of application/json and RestTemplate was able to process the response with no issues into a Map.


I ended up getting the contents as a String and then converting them to a Map like this:

String json = restTemplate.getForObject(buildUrl, String.class);
Map<String,String> map = new HashMap<String,String>();
ObjectMapper mapper = new ObjectMapper();

try {
    //convert JSON string to Map
   map = mapper.readValue(json, new TypeReference<HashMap<String,String>>(){});
} catch (Exception e) {
     logger.info("Exception converting {} to map", json, e);
}

return map;
Rhyner answered 13/6, 2014 at 15:55 Comment(3)
This is redundant and unnecessary. You're creating and using an ObjectMapper almost exactly like the RestTemplate already should be doing.Springtail
This should not be the accepted answer, you should not need to write code to do this!Elka
@Elka I accepted my own answer because it worked for me. I've found many a time that I just need to get work done and move on. I haven't tested Sotirios Delimanolis answer but I suspect he's correct.Rhyner
T
0

I know its old, but just for other people that may visit this topic: If you want to register some additional converters with RestTemplateBuilder you also have to explicitly register default ones

@Bean
public RestTemplateBuilder builder() {
    return new RestTemplateBuilder()
            .defaultMessageConverters()
            .additionalMessageConverters(halMessageConverter());
}

private HttpMessageConverter halMessageConverter() {
    ObjectMapper objectMapper = new ObjectMapper().registerModule(new Jackson2HalModule());
    TypeConstrainedMappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
    halConverter.setSupportedMediaTypes(Collections.singletonList(MediaTypes.HAL_JSON));
    halConverter.setObjectMapper(objectMapper);
    return halConverter;
}
Toomey answered 10/11, 2016 at 10:14 Comment(0)
Q
0

This worked 100% for me

in client

Map<String, Object> mapRespuesta = new HashMap<>();
mapRespuesta.put("mensaje", "Process completed successfully");
mapRespuesta.put("idResponse", id);
return new ResponseEntity<Map<String, Object>>(mapRespuesta, HttpStatus.OK);

in which it makes the connection

ResponseEntity<Map> result = restTemplate.postForEntity(url, params, Map.class);
String id = result.getBody().get("idResponse").toString();
Queasy answered 12/10, 2022 at 23:29 Comment(0)
I
-1
@GetMapping(value = "getSunny/{userId}")
    public Map<String,  SunnyVO> getSunny(@PathVariable int sunnyId) {
        
        Map<String,  SunnyVO> newObj = new HashMap<String, SunnyVO>();
        final String url = "http://localhost:8085/Sunny/getSunny/{sunnyId}";
        RestTemplate restTemplate = new RestTemplate();
        newObj = restTemplate.getForObject(url, Map.class, sunnyId);
        return newObj;

    }

It is working for me ...

Ice answered 1/9, 2020 at 7:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.