0

Background

I have the following code that uses RestTemplate to publish requests to a web service.

public class Service {

    public static OutputDTO calculate(InputDTO inputDTO) throws ProcessException {

        OutputDTO outputDto = null;
        try {
            RequestProperties props = RequestProperties.getPropsInstance();
            String requestProcessorUrl = props.getRequestProcessorUrl();
            RestTemplate restTemplate = new RestTemplate();
            MappingJackson2HttpMessageConverter httpMessageconvertor = new MappingJackson2HttpMessageConverter();
            restTemplate.getMessageConverters().add(httpMessageconvertor);
            List<MediaType> mediaTypeList = new ArrayList<>();
            mediaTypeList.add(MediaType.APPLICATION_JSON);
            httpMessageconvertor.setSupportedMediaTypes(mediaTypeList);
            outputDto = restTemplate.postForObject(requestProcessorUrl, inputDTO, OutputDTO.class);
        } catch (ApplicationPropertiesException e) {
            throw new ProcessException("PublisherService calculate() caught ApplicationPropertiesException", e, 1);
        } catch (Exception e) {
            throw new ProcessException("PublisherService calculate() caught unexpected Exception", e, 1);
        }

        return outputDto;
    }
}

The above code is called concurrently by multiple threads each passing an InputDTO that is the input request object to be sent to the Web Service. The problem I am facing is that I sometimes get a 400 bad request when one of the threads calls the calculate method for the same input data set. That is, the code works perfectly fine for one end to end run but fails with a 400 bad request with the same input data set for other end to end runs.

Stack Trace

Caused by: com.mycompany.adapter.processor.ProcessException: Service calculate() caught unexpected Exception at com.mycompany.adapter.service.Service.calculate(Service.java:42)
at com.mycompany.adapter.processor.Processor$RequestSender.send(Processor.java:430) ... 6 more Caused by: org.springframework.web.client.HttpClientErrorException: 400 Bad Request at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91) at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:641) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:597) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557) at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:357) at com.mycompany.adapter.service.Service.calculate(Service.java:38)

Edit : What I have tried so far

I added the following code in the catch(Exception e) block shown in the code above :

catch (Exception e) {

                ObjectMapper mapper = new ObjectMapper();
                String jsonInString="";
                try {
                    jsonInString = mapper.writeValueAsString(inputDTO);
                } catch (JsonProcessingException e) {
                    logger.log(Level.SEVERE, "Exception while trying to convert dto to json ", ((Throwable)e));
                } 

               try(BufferedWriter br = new BufferedWriter(new FileWriter("json.txt",true))) {
                br.write("jsonstring is --> "+jsonInString);
               } catch(Exception e) {
                    logger.log(Level.SEVERE, "Exception while trying to write json ", ((Throwable)e));
               }
}

My expectation here was that in case of a "bad request error", the call to mapper.writeValueAsString(inputDTO) would have thrown an exception but this was not the case as the corresponding log statement in the catch block surrounding this statement was not printed.

Next, as you can see, I dumped the json string that was causing the bad request issue into a file to investigate if it is malformed. I tried parsing the json string into a JSON object/array using this approach it and it came out as a valid JSON string.

Question(s)

  1. Are the RestTamplate and related Spring APIs that I am using in my code above thread-safe?
  2. What could be the reason behind a bad request error occurring for one run whereas other runs with the same input data set work fine?
  3. What further investigation can I carry to narrow down the issue? I tried finding the InputDTO objects for which I get the bad request exception. I then ran my code only for those records but the bad request error is not reproducible when I take the same records and run the code only for these records.
  4. One of the comments to this answer states that this is a concurrency issue but as explained earlier, the JSON string causing the bad request is apparently a valid string if you try parsing it. Moreover, I am absolutely sure that the inputDTO is not modified by any other thread as each thread creates it's own inputDTO locally in its run/call method and passes it to the calculate method. The inputDTO is not shared across threads.
Ping
  • 587
  • 5
  • 27
  • 1
    Your given method looks thread safe. But in case if you are changing same "InputDTO inputDTO" somewhere else which is being used by current thread at the same time, then this issue can occur. – Afridi Dec 27 '17 at 07:38
  • What happens in the `RequestProperties props = RequestProperties.getPropsInstance(); String requestProcessorUrl = props.getRequestProcessorUrl();` calls? Any thread local operations? What happens if you use constant URL? – StanislavL Dec 27 '17 at 08:45
  • @StanislavL It reads the url from the only property file that I have in my project so it is as good reading a constant url. – Ping Dec 27 '17 at 09:31
  • @Afridi The code that creates the `inputDTO` is thread safe. Even if it wasn't, the worst that could happen is that inconsistent values get stamped into an attribute in the `inputDTO`. Why should it result in a bad request error? – Ping Dec 27 '17 at 09:35
  • @Ping Generally Bad Request error mean parameters data in your request is either missing or invalid. May be one thread after completing rest operation, changing data in input data, while another thread is trying to send that input data(which is already modified, in case if both threads are working on same data). So make it sure that your input data is not changing after rest operation – Afridi Dec 27 '17 at 10:10
  • @Afridi That is what I mentioned in my comment, even if threads modify the same `inputDTO`, it would only result in incorrect values for the attributes. Example thread 1 sets inputDTO.price to 1 and thread 2 sets inputDTO.price to 2. The end result is price being set to 2. How does that result in a bad request? There would eventually be some value stamped into the attributes. Doesn't matter if it is logically correct. – Ping Dec 27 '17 at 10:14
  • @Ping Yes, sure it will not result in error. I am trying to say if one thread is trying to just sending data using rest while another thread have completed that operation and is trying to reset some or all parameters of that DTO. In that case, this error can occurs. – Afridi Dec 27 '17 at 10:17
  • @Afridi I disagree. I don't see why a request should become a bad request in that scenario. Let us wait for other users to comment/provide other possible reasons. – Ping Dec 27 '17 at 10:23
  • @Afridi See my edit. I tried dumping the JSON string that was causing the "bad request" into a file and tried validating it independently. It turns out the JSON string is valid. Do you still feel this is a concurrency issue considering I just demonstrated that the bad request issue occurs for a valid JSON string? Also, I can confirm that the `inputDTO` is a local object and no other thread modifes it once it is created. – Ping Jan 02 '18 at 07:10
  • If server returned you response with Bad Request, it doesn't always mean your JSON data is incorrect(i.e json data is invalid/not-parsable as you are thinking). In most cases it might be your json data is valid/parsable, but still server can respond with such message. So possible reasons can be: 1. Some of required params in request are missing. 2. Server haven't properly handled exceptions(if you have designed it yourself). So best approach would be to catch that exception(HttpClientErrorException) and get response data/message – Afridi Jan 02 '18 at 12:18
  • @Afridi "Some of the required parameters in request are missing" Can you elaborate on what these parameters would be? As I mentioned, the client randomly fails with the same input data set and zero code changes. If there were some missing parameters, wouldn't the code fail "always"? – Ping Jan 02 '18 at 15:29

0 Answers0