skip to Main Content

I’m having trouble with the response of an external REST API and rest clients from Spring. I’m currently using the new (+3.2.0 if I’m not mistaken) "Rest Client", but I’ll also accept answers that solve the problem for "Web Client", if it applies and you coudn’t find an answer for the new Rest Client.

Simply put, I make a request to an external REST API that returns a body. The content-type header reads:

application/json; charset=utf-8

The body itself actually isn’t your normal json that the Web Client or Rest Client would (I suppose) be able to deseralize without problem. It seems to be a text json, even though that media type in itself doesn’t exist. An example of the response body:

"{"Code":"123-456","Number":1}"

Let me reiterate that that is the literal response, not this:

{
  "Code": "123-456",
  "Number": 1
}

I tried this and didn’t work:

{
  Object response = restClient.post()
    .uri(url)
    .contentType(MediaType.APPLICATION_JSON)
    .body(request)
    .retrieve()
    .body(ApiResponse.class);
}

I also tried:

{
  Object response = restClient.post()
    .uri(url)
    .contentType(MediaType.APPLICATION_JSON)
    .body(request)
    .retrieve()
    .body(String.class);
}

Both of these throw the same error, they break at the .body(...) part. I’ve also tried .toEntity(...), and it breaks there too. I’ll just show the one for the first one:

Error while extracting response for type [com.example.test.models.ApiResponse] and content type [application/json;charset=utf-8]

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (jdk.internal.net.http.ResponseSubscribers$HttpResponseInputStream); line: 1, column: 1]

My guess is that, because the response’s content-type header is application/json, it expects a json, that starts with {. However, the response starts with ", because, like I said, it’s not a normal json, it’s a json in string literal format. Thus the Cannot deserialize value of type 'java.lang.String' from Object value (token 'JsonToken.START_OBJECT').

I managed to make it work changing to "Rest Template" and doing something else, but I don’t want to do that. I want to make this work with Rest Client/Web Client.

Pls help, I have no idea what else to do. Also, is there any reason why someone would do this intentionally? I mean, making the response a json string instead of a normal json. I wouldn’t be writting this right now if this external REST API responded with a normal json (i would guess).

Edit: I’m using the default Rest Client with the default Message Converters.

Edit 2: I investigated a bit and I know what was throwing this error. I will leave it in an answer below. However, there’s still a problem, being more on par with the title of the question. I’ll leave it there as well.

Edit 3: If you have time to downvote you have time to help or at least leave a comment as to why. The problem isn’t solved yet. If you’re not going to try and help, then don’t bother and get lost. That’s my personal opinion.

2

Answers


  1. Chosen as BEST ANSWER

    So, it turns out this external API was returning 500 instead of 200. That triggered an error handler that I had, and I didn't know. The thing is that I was able to inspect, within the error handler, the error message. With that, I was able to solve this 1st problem.

    As it turns out, this external API was expecting the request body to be a string, not an object.

    Expected this:

    String responseString = restClient.post()
        .uri(url)
        .contentType(MediaType.APPLICATION_JSON)
        .body(request.toJsonString()) // <-- method to convert it to a json string that I created
    //...
    

    Not this:

    String responseString = restClient.post()
        .uri(url)
        .contentType(MediaType.APPLICATION_JSON)
        .body(request) // <-- original object from ApiRequest class
    //...
    

    So yeah, sorry for wasting your time, and god damn this API, poorly documented and with some questionable design choices.

    But now I have another problem, and it's more related to the title question. Now with this code:

    Object response = restClient.post()
        .uri(url)
        .contentType(MediaType.APPLICATION_JSON)
        .body(request.toJsonString())
        .retrieve()
        .body(ApiResponse.class);
    

    In the .body(...) part I'm getting the following exception:

    org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.test.models.ApiResponse` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{"Code":"123-456","Number":1}')
    

    The ApiResponse class looks like this:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class ApiResponse {
      @JsonProperty("Code")
      private String code;
      @JsonProperty("Number")
      private String number;
    }
    

    The thing that the external API responds is literally (according to Postman):

    "{"Code":"123-456","Number":1}"
    

    Am I missing something?


  2. I haven’t been able to try the solution but it might give you ideas :

    Have you tried using bodyToMono(String.class) instead? You can follow it by .block() even if it’s generally not recommended.

    Then convert your responseString to a json using jackson like :

    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import reactor.core.publisher.Mono;
    
    // ...
    
    String responseString = restClient.post()
        .uri(url)
        .contentType(MediaType.APPLICATION_JSON)
        .body(request)
        .retrieve()
        .bodyToMono(String.class)
        .block();
    
    ObjectMapper objectMapper = new ObjectMapper();
    JsonNode responseJson = objectMapper.readTree(responseString);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search