java.lang.IllegalArgumentException: Comparison method violates its general contract! in Spring Rest Template - spring-boot

I am facing a weird issue while calling a REST url using Spring's RestTemplate.I am using Spring Boot.
The error is occurring only after approx 10 to 15 successful calls and thereafter erratically. I can smoothly exchange data before the error, in the first 1 to 15 calls approx. Url is like someresturl/param1/param2/param3.
public ResponseEntity<String> callRestUrl(CustomReqClass req) {
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
StringBuilder url = new StringBuilder("someresturl");
finishTaskUrl.append("/").append(param1).append("/").append(param2).append("/").append(param3);
ResponseEntity<String> response = null;
HttpEntity<CustomReqClass> request = new HttpEntity<CustomReqClass>(req, getHTTPHeaders());
try {
//first approach
response = restTemplate.postForEntity(url.toString(), request, String.class, Collections.<String, String>emptyMap());
//second approach
response = restTemplate.exchange(url.toString(), HttpMethod.POST, request, String.class);
} catch (Exception e) {
LOGGER.info("Error calling url" + e);
}
return response;
}
public MultiValueMap<String, String> getHTTPHeaders() {
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Authorization", "Basic authabcdxyz");
headers.add("Content-Type", "application/json");
return headers;
}
Here I am autowiring restTemplate object in the class where I am using this.
I have tried both the above methods postForEntity and exchange of Rest template. Error is occurring for both.
The exception I am getting after first few successful attempts:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
As an additional thought, the above piece of code is being scheduled by Spring Scheduler mechanism. Is it possible internal threading used in scheduler is causing this issue?

Related

How to handle the http response from an api call efficiently using spring boot

When we fire an api call to a 3rd party service, we can get different HTTP responses (200, 404 etc.). How can we handle them in a standard way?
private ResponseEntity<ResultHolder> responseEntity;
public ResponseEntity<ResultHolder> serviceTest(String searchText, String countryCode) {
logger.info("Service started");
String url = prepareUrl(searchText,countryCode); //custom method
HttpHeaders header = new HttpHeaders();
prepareHeader(header); //custom method
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
ResultHolder.class);
}catch (Exception e) {
logger.error("Exception while calling the API "+ e);
//Here I am trying to get the value of response code and handle based on that
//Is this the right way to solve the problem?
if(responseEntity.getStatusCodeValue() != 200) {
responseEntity = new ResponseEntity<ResultHolder>(
HttpStatus.BAD_REQUEST);
}
}
logger.info("Service Ended");
return responseEntity;
}
What if I want to display distinct custom messages for server side errors and for user errors like 'No Internet Connection'.
Kindly help me to understand the good practises in this area.
Thank you

Sending request with headers to third parts api with WebClient

I really like the solution I have with RestTemplate but soon it will be depreciated with future spring releases. I am trying to send some text to a third party api using WebClient
String text = URLEncoder.encode(text,"UTF-8");
WebClient webClient = WebClient.builder()
.baseUrl(BASE_URL)
.defaultHeader("Key","af999-e99-4456-b556-4ef9947383d")
.defaultHeader("src", srcLang)
.defaultHeader("tgt", tgtLang)
.defaultHeader("text", text)
.build();
Then send a post here:
Mono<String> response = webClient.post().uri("/google/rtv/text")
.retrieve()
.bodyToMono(String.class);
Trying to parse based off of the legacy response:
private String parseJson( Mono<String> response) {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = null;
JsonNode review = null;
//TODO: create an object and map it here. We need to save the original review too.
try {
root = mapper.readTree(response.toString());
review = root.path("message");
} catch (IOException e) {
e.printStackTrace();
}
return review.asText();
}
Later I need to parse the response but right now I am getting an error saying:
com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'MonoFlatMap': was expecting ('true', 'false' or 'null')
at [Source: (String)"MonoFlatMap"; line: 1, column: 23]
and later:
java.lang.NullPointerException: null
What I am trying to accomplish is something like I have done with RestTemplate.
Like so:
UriComponentsBuilder builder = UriComponentsBuilder
.fromUriString(URL)
.queryParam("src", src)
.queryParam("tgt", tgt)
.queryParam("text", text);
ResponseEntity<String> response = restTemplate.exchange(builder.toUriString(), HttpMethod.GET, request, String.class);
then set my header for the subscription globally.
private ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
request.getHeaders().add("Key","af999-e99-4456-b556-4ef9947383d");
ClientHttpResponse response = execution.execute(request, body);
return response;
}
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(this::intercept));
return restTemplate;
}
Advice?
The problem happens here:
root = mapper.readTree(response.toString());
This code snippet is trying to serialize a Mono<String> as a String, when a Mono is a reactive type that can provide that String value eventually.
You could call response.block() and getting the resulting String, but this would be a blocking call and Reactor forbids that if in the middle of a reactive execution. This is done for good reasons, since this will block one of the few threads that your web application is using and can cause it to stop serving other requests.
You could instead have something like:
Mono<String> review = response.map(r -> parseJson(r);
And then reuse that new value down the line.
Note that WebClient natively supports JSON deserialization and you could deserialize the whole payload like so:
Mono<Review> review = webClient.post().uri("/google/rtv/text")
.retrieve()
.bodyToMono(Review.class);

How to get custom messages when you use response.getStatusText()

HttpClientErrorException always produces the following result for me:
HttpClientErrorException: 400 null
... and the null part is what worries me. Shouldn't this be the place where the message of the server-side exception is supposed to be?
I checked the source code of the HTTP client to see where the client-side exception is thrown. It looks like this:
throw new HttpClientErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
Debugging this call revealed that response.getStatusText() is null in my case.
My question is: How do you design your ResponseEntity on the server-side such that the HTTP client finds the server-side exception message in response.getStatusText() instead of null?
Here is my Exception
#ExceptionHandler({ MyCustomException.class })
public ResponseEntity<String> handleException(final HttpServletRequest
req, final MyCustomException e) {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-type", "text/plain");
String body = e.toString();
return new ResponseEntity<>(body, headers, HttpStatus.BAD_REQUEST);
}

RestTemplate call returns 401 Unauthorized

Background
I am trying to consume a REST endpoint hosted on IBM Cloud API from my SpringBoot application using RestTemplate. I am using the following snippet to make the call:
RestTemplate send = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setCacheControl(CacheControl.noCache());
headers.set("x-ibm-client-id", clientId);
headers.set("x-ibm-client-secret", clientSecret);
HttpEntity<BodyEntity> httpEntity = new HttpEntity<>(bodyEntity, headers);
send.exchange(ENDPOINT_URL, HttpMethod.POST, httpEntity, Object.class);
I used the following snippet to configure RestTemplate
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Problem
Using this snippet, when the call is made I receive 401 Unauthorized. When I made the same call using Postman, I received correct response from server without any problem.
Since I received 401 response code I set to further investigate the request by logging headers and body and other parts of request.
I implemented ClientHttpRequestInterceptor to log outgoing requests to further debug the issue and added this interceptor to my RestTemplate config as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
// new code
builder.interceptors(new LoggingClientHttpRequestInterceptor());
return builder.build();
}
After making the request again, I could see in the log that the outgoing call contained all details as it should e.g. Headers and Body were correct.
After this, I changed the whole thing to use Apache HTTP Client as follows:
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(URL);
String reqString = "BODY";
httpPost.setEntity(new StringEntity(reqString, ContentType.APPLICATION_JSON));
httpPost.setHeader("accept", "application/json");
httpPost.setHeader("content-type", "application/json");
httpPost.setHeader("cache-control", "no-cache");
httpPost.setHeader("x-ibm-client-id", clientId);
httpPost.setHeader("x-ibm-client-secret", clientSecret);
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
System.out.println("Response status: " + response.getStatusLine());
HttpEntity entity1 = response.getEntity();
System.out.println("Response :" + entity1.toString());
} finally {
response.close();
}
Using the snippet above, I executed the request and received correct response.
Question
Why RestTemplate call returns and error whereas HttpClient returns correct response?
Do I need to further configure RestTemplate?
What have I missed?

RestTemplate - handle potential NullPointerException when response body is null

I'm writing client that calls some backend REST service. I'm sending Product object which will be saved in DB and returned in response body with generated productId.
public Long createProduct(Product product) {
RestTemplate restTemplate = new RestTemplate();
final String url = " ... ";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Product> productEntity = new HttpEntity<>(product, headers);
try {
ResponseEntity<Product> responseEntity = restTemplate.postForEntity(url, productEntity, Product.class);
Product product = responseEntity.getBody();
return product.getProductId();
} catch (HttpStatusCodeException e) {
logger.error("Create product failed: ", e);
throw new CustomException(e.getResponseBodyAsString(), e, e.getStatusCode().value());
}
This product.getProductId() looks like potential NullPointerException if product i.e. responseEntity.getBody() is null, should I handle it somehow?
I have looked examples over internet of using RestTemplate postFprEntity, getForEntity ... but didn't find any example that handle NPE. I suppose that if body of response cannot be set, it will be some exception thrown and status code 5xx.
Is it possible when response status code is 200, that body can be null?
Is it possible when response status code is 200, that body can be
null?
Yes, it is quite possible and totally depends on the server. Normally, some REST APIs and Spring REST Repositories will return 404 if resource is not found but better safe than sorry.
This product.getProductId() looks like potential NullPointerException
if product i.e. responseEntity.getBody() is null, should I handle it
somehow?
Of course you should.
You can check if responseEntity.hasBody() && responseEntity.getBody() != null. And from there either throw an Exception of your own or handle however you see fit.

Resources