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
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:
Not this:
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:
In the
.body(...)
part I'm getting the following exception:The ApiResponse class looks like this:
The thing that the external API responds is literally (according to Postman):
Am I missing something?
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 :