11

I have created a Spring Restful Service and Spring MVC application.

Restful Service :: Restful service returns an entity if its existing in DB. If it doesn't exist It returns a custom Exception information in ResponseEntity object.

It is working as expected tested using Postman.

@GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(@PathVariable Long itemId, @RequestHeader HttpHeaders httpHeaders) {

    MyItem myItem = myitemService.validateMyItem(itemId);
    ResponseEntity<MyItem> responseEntity = null;
    if (myItem == null) {
        throw new ItemNotFoundException("Item Not Found!!!!");
    }
    responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
    return responseEntity;
}

If the requested Entity does not exist Restful Service returns below.

@ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
    System.out.println("In CREEH::ItemNFE");
    ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
    ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
    return responseEntity;
}

But when I am calling the above service from a spring MVC application using RestTemplate, It is returning a valid object if it exists.

If the requested object does not exist Restful service is returning the exception information but its not reaching the calling(spring MVC) application.

Spring MVC application calls Restful Web Service using Rest template

String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();
Phenomenal One
  • 2,501
  • 4
  • 19
  • 29
Abdul
  • 1,130
  • 4
  • 29
  • 65
  • It may be that spring is not able to serialize your exception into a JSON body in your response. Can you remove `produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }` and check if it will create a response? – T A Sep 05 '18 at 11:14
  • is the exception handler defined in the same controller, or do you have `@ControllerAdvice` class? – kukkuz Sep 05 '18 at 11:25
  • @TA, I tried your suggestion , not working. – Abdul Sep 05 '18 at 11:40
  • kukkuz, I have a separate @RestControllerAdvice class – Abdul Sep 05 '18 at 11:43
  • @Abdul so the exception handler is in the `@RestControllerAdvice` class and the *validateItem* is in a `@RestController` class? just checking – kukkuz Sep 05 '18 at 11:47
  • Yes kukkuz, validateItem is in RestController and exception handler is in RestControllerAdvice – Abdul Sep 05 '18 at 11:52
  • @Abdul I suggest you to have a common DTO class (int statusCode,String message,Object response) for the client server communication. In exception handler create common DTO object and instead of returning `ResponseEntity` return your common DTO object. – Sharan De Silva Sep 05 '18 at 12:33
  • @Abdul do you have error in your client App, where you call int restCallStateCode = responseEntity.getStatusCodeValue(); ?? – Moler Sep 05 '18 at 13:56
  • Moler, Yes i have a same copy of Exception Class in client app as well. – Abdul Sep 06 '18 at 04:59
  • Sharan, If I retrun a common DTO with Exception details then how I can retrun actual entity in a success scenario. Thank you. – Abdul Sep 06 '18 at 05:01
  • can you update your exception handler class@Abdul – Ryuzaki L Sep 28 '18 at 23:54

4 Answers4

10

This is expected behavior. Rest template throws exception when the http status is client error or server error and returns the response when http status is not error status.

You have to provide implementation to use your error handler, map the response to response entity and throw the exception.

Create new error exception class with ResponseEntity field.

public class ResponseEntityErrorException extends RuntimeException {
  private ResponseEntity<ErrorResponse> errorResponse;
  public ResponseEntityErrorException(ResponseEntity<ErrorResponse> errorResponse) {
      this.errorResponse = errorResponse;
  }
  public ResponseEntity<ErrorResponse> getErrorResponse() {
      return errorResponse;
  }
}

Custom error handler which maps the error response back to ResponseEntity.

public class ResponseEntityErrorHandler implements ResponseErrorHandler {

  private List<HttpMessageConverter<?>> messageConverters;

  @Override
  public boolean hasError(ClientHttpResponse response) throws IOException {
    return hasError(response.getStatusCode());
  }

  protected boolean hasError(HttpStatus statusCode) {
    return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
  }

  @Override
  public void handleError(ClientHttpResponse response) throws IOException {
    HttpMessageConverterExtractor<ExceptionResponse> errorMessageExtractor =
      new HttpMessageConverterExtractor(ExceptionResponse.class, messageConverters);
    ExceptionResponse errorObject = errorMessageExtractor.extractData(response);
    throw new ResponseEntityErrorException(ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(errorObject));
  }

  public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    this.messageConverters = messageConverters;
  }
}

RestTemplate Configuration - You have to set RestTemplate's errorHandler to ResponseEntityErrorHandler.

@Configuration
public class RestTemplateConfiguration {
  @Bean
  public RestTemplate restTemplate() {
      RestTemplate restTemplate = new RestTemplate();
      ResponseEntityErrorHandler errorHandler = new ResponseEntityErrorHandler();
      errorHandler.setMessageConverters(restTemplate.getMessageConverters());
      restTemplate.setErrorHandler(errorHandler); 
      return restTemplate;
   }
}

Calling Method

@Autowired restTemplate

String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
try {
    ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
    int restCallStateCode = responseEntity.getStatusCodeValue();
} catch (ResponseEntityErrorException re) {
    ResponseEntity<ErrorResponse> errorResponse = re.getErrorResponse();
}
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • hi Veeram, the ResponseEntityErrorException should be at server side and ResponseEntityErrorHandler should be implemented at client side right? – Abdul Sep 29 '18 at 03:23
  • No changes on server side. Everything is on client side.Note the use of exception inside error handler. – s7vr Sep 29 '18 at 04:50
  • I am really very thankful to you. I am implementing this in app. Can you please let me know how I can do this ** errorHandler.setMessageConverters(restTemplate.getMessageConverters()); ** in xml. – Abdul Sep 29 '18 at 12:41
  • I created all the required code setup now I am able see the flow in your classes, But Because of I am not getting to set the message converters in ErrorHandler it throwing this error - Request processing failed; nested exception is java.lang.IllegalArgumentException: 'messageConverters' must not be empty – Abdul Sep 29 '18 at 13:54
  • Unfortunately xml doesn't give that level of control. You've to add message converters manually to error handler. – s7vr Sep 29 '18 at 15:20
  • Something like ` ` and ` `. Add more converters should you need. – s7vr Sep 29 '18 at 15:21
  • I really appreciate and very thankful to your time. It helped me a lot and give the solution exactly what i was looking for. Thank you very much. Thanks a lot. – Abdul Sep 29 '18 at 16:25
  • Hi, im getting following errors : Cannot resolve symbol 'ErrorResponse' Cannot resolve symbol 'ExceptionResponse' – Anant Vikram Singh Apr 22 '22 at 09:48
  • Hi @s7vr , can you please help me the following errors : Cannot resolve symbol 'ErrorResponse' Cannot resolve symbol 'ExceptionResponse' Are these two POJOs and if yes then what are the attributes ? – Anant Vikram Singh Apr 25 '22 at 05:45
  • @s7vr Actually, the server is only throwing 500 Internal Server error to us for all kinds of different errors due to which we are not able to report the correct error. It's a SOAP based request and when we are using SOAP UI, we are able to get those different errors but we are not able to get those errors when hitting the API through JAVA code. – Anant Vikram Singh Apr 25 '22 at 05:48
1

Try using the @ResponseBody annotation on your Exceptionhandler. e.g:

public @ResponseBody ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {... }
  • I tried, but Its not working because I am already doing that. I am using @RestControllerAdvice – Abdul Sep 05 '18 at 11:38
0

I've started your application and works just fine.

Maven :

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

The controller class is :

@Controller
public class ValidationController {


    @GetMapping(value = "/validate/{itemId}")
    public @ResponseBody ResponseEntity<MyItem> validateItem(@PathVariable Long itemId) {
        if (itemId.equals(Long.valueOf(1))) {
            throw new ItemNotFoundException();
        }
        return new ResponseEntity<>(new MyItem(), HttpStatus.OK);
    }

    @ExceptionHandler(ItemNotFoundException.class)
    public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
        System.out.println("In CREEH::ItemNFE");
        ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
        ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
        return responseEntity;
    }
}

and the test:

@RunWith(SpringRunner.class)
@WebMvcTest(value = ValidationController.class, secure = false)

public class TestValidationController {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testExpectNotFound() throws Exception {
        mockMvc.perform(get("/validate/1"))
                .andExpect(status().isNotFound());
    }

    @Test
    public void testExpectFound() throws Exception {
        mockMvc.perform(get("/validate/2"))
                .andExpect(status().isOk());
    }
}

Are you sure the url you are trying to use with RestTemplate is correct?

 String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";

Your get method is @GetMapping(value = "/validate/{itemId}"

If you don't have request mapping at the level of the controller the url should be:

 http://localhost:8080/validate/1

Another difference is the missing @ResponseBody on your controller method.

Adina Rolea
  • 2,031
  • 1
  • 7
  • 16
0

You should use Custom Exception Handler to fix your case. It looks like this

@ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    public CustomResponseEntityExceptionHandler() {
        super();
    }
    // 404
    @ExceptionHandler(value = { EntityNotFoundException.class, ResourceNotFoundException.class })
    protected ResponseEntity<Object> handleNotFound(final RuntimeException ex, final WebRequest request) {      
        BaseResponse responseError = new BaseResponse(HttpStatus.NOT_FOUND.value(),HttpStatus.NOT_FOUND.name(),
                Constants.HttpStatusMsg.ERROR_NOT_FOUND);       
        logger.error(ex.getMessage());
        return handleExceptionInternal(ex, responseError, new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

}

And your code should throw some exception, eg:

if (your_entity == null) {
    throw new EntityNotFoundException("said something");
}

If you get this case in somewhere else again, you just throw exception like above. Your handler will take care the rest stuffs.

Hope this help.

David Pham
  • 1,673
  • 19
  • 17