48

I have a rest service which send an 404 error when the resources is not found. Here the source of my controller and the exception which send Http 404.

@Controller
@RequestMapping("/site")
public class SiteController
{

    @Autowired
    private IStoreManager storeManager;

    @RequestMapping(value = "/stores/{pkStore}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public StoreDto getStoreByPk(@PathVariable long pkStore) {       
        Store s = storeManager.getStore(pkStore);
        if (null == s) {
            throw new ResourceNotFoundException("no store with pkStore : " + pkStore);
        }
        return StoreDto.entityToDto(s);       

    }
}
 
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException
{       
    private static final long serialVersionUID = -6252766749487342137L;    
    public ResourceNotFoundException(String message) {
        super(message);
    }    
}

When i try to call it with RestTemplate with this code :

ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
 System.out.println(r.getStatusCode());
 System.out.println(r.getBody());

I receive this exception :

org.springframework.web.client.RestTemplate handleResponseError
ATTENTION: GET request for "http://........./stores/99" resulted in 404 (Introuvable); invoking error handler
org.springframework.web.client.HttpClientErrorException: 404 Introuvable

I was thinking I can explore my responseEntity Object and do some things with the statusCode. But exception is launch and my app go down.

Is there a specific configuration for restTemplate to not send exception but populate my ResponseEntity.

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
loic
  • 511
  • 1
  • 4
  • 5

7 Answers7

58

As far as I'm aware, you can't get an actual ResponseEntity, but the status code and body (if any) can be obtained from the exception:

try {
    ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
}
catch (final HttpClientErrorException e) {
    System.out.println(e.getStatusCode());
    System.out.println(e.getResponseBodyAsString());
}
djm.im
  • 3,295
  • 4
  • 30
  • 45
Squatting Bear
  • 1,644
  • 14
  • 12
21

RESTTemplate is quite deficient in this area IMO. There's a good blog post here about how you could possibly extract the response body when you've received an error:

http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

As of today there is an outstanding JIRA request that the template provides the possibility to extract the response body:

https://jira.spring.io/browse/SPR-10961

The trouble with Squatting Bear's answer is that you would have to interrogate the status code inside the catch block eg if you're only wanting to deal with 404's

Here's how I got around this on my last project. There may be better ways, and my solution doesn't extract the ResponseBody at all.

public class ClientErrorHandler implements ResponseErrorHandler
{
   @Override
   public void handleError(ClientHttpResponse response) throws IOException 
   {
       if (response.getStatusCode() == HttpStatus.NOT_FOUND)
       {
           throw new ResourceNotFoundException();
       }

       // handle other possibilities, then use the catch all... 

       throw new UnexpectedHttpException(response.getStatusCode());
   }

   @Override
   public boolean hasError(ClientHttpResponse response) throws IOException 
   {
       return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
         || response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
   }

The ResourceNotFoundException and UnexpectedHttpException are my own unchecked exceptions.

The when creating the rest template:

    RestTemplate template = new RestTemplate();
    template.setErrorHandler(new ClientErrorHandler());

Now we get the slightly neater construct when making a request:

    try
    {
        HttpEntity response = template.exchange("http://localhost:8080/mywebapp/customer/100029",
                                        HttpMethod.GET, requestEntity, String.class);
        System.out.println(response.getBody());
    }
    catch (ResourceNotFoundException e)
    {
        System.out.println("Customer not found");
    }
aboger
  • 2,214
  • 6
  • 33
  • 47
Dick Chesterwood
  • 2,629
  • 2
  • 25
  • 33
11

Since it's 2018 and I hope that when people say "Spring" they actually mean "Spring Boot" at least, I wanted to expand the given answers with a less dust-covered approach.

Everything mentioned in the previous answers is correct - you need to use a custom ResponseErrorHandler. Now, in Spring Boot world the way to configure it is a bit simpler than before. There is a convenient class called RestTemplateBuilder. If you read the very first line of its java doc it says:

Builder that can be used to configure and create a RestTemplate. Provides convenience methods to register converters, error handlers and UriTemplateHandlers.

It actually has a method just for that:

new RestTemplateBuilder().errorHandler(new DefaultResponseErrorHandler()).build();

On top of that, Spring guys realized the drawbacks of a conventional RestTemplate long time ago, and how it can be especially painful in tests. They created a convenient class, TestRestTemplate, which serves as a wrapper around RestTemplate and set its errorHandler to an empty implementation:

private static class NoOpResponseErrorHandler extends 
       DefaultResponseErrorHandler {

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
    }

}
yuranos
  • 8,799
  • 9
  • 56
  • 65
  • The more recently-available code here is nice to know, but I don't see how its any better than setting a ResponseErrorHandler on an already-instantiated RestTemplate, rather than before instantiated with RestTemplateBuilder#errorHandler as described here - you get a ResponseErrorHandler set on your RestTemplate somehow or another. So I'm not sure what was so outdated with the so-called "dust-covered" approaches. TestRestTemplate sounds like it could be useful. – cellepo May 08 '18 at 20:24
  • For the sake of the more recently-available code, I'm up-voting this. I was just trying to emphasize above that I don't see anything "better" with the newer code. – cellepo May 08 '18 at 20:26
  • Sure, @cellepo, as I said in the anwer, it's all the same. The error handler solution might sound like a more solid solution to some people when they see that it's the way Spring guys do it as well. Hence my answer. Thanks for the upvote! – yuranos May 08 '18 at 20:54
4

You can create your own RestTemplate wrapper which does not throw exceptions, but returns a response with the received status code. (You could also return the body, but that would stop being type-safe, so in the code below the body remains simply null.)

/**
 * A Rest Template that doesn't throw exceptions if a method returns something other than 2xx
 */
public class GracefulRestTemplate extends RestTemplate {
    private final RestTemplate restTemplate;

    public GracefulRestTemplate(RestTemplate restTemplate) {
        super(restTemplate.getMessageConverters());
        this.restTemplate = restTemplate;
    }

    @Override
    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.getForEntity(url, responseType));
    }

    @Override
    public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.postForEntity(url, request, responseType));
    }

    private <T> ResponseEntity<T> withExceptionHandling(Supplier<ResponseEntity<T>> action) {
        try {
            return action.get();
        } catch (HttpClientErrorException ex) {
            return new ResponseEntity<>(ex.getStatusCode());
        }
    }
}
siledh
  • 3,268
  • 2
  • 16
  • 29
  • How to consume this class in the controller? Autowired of this entity goes to the usual implementation of getForEntity and returns the exception. – Karthikeyan Feb 08 '20 at 18:41
  • I just create it manually using the constructor and passing my usual RestTemplate. Note that you will need to override more methods, since there are many similar ones. – siledh Feb 26 '20 at 10:31
  • My issue was a blender mistake. I was calling a different method instead of overridden one. – Karthikeyan Feb 27 '20 at 05:36
1

Recently had a usecase for this. My solution:

public class MyErrorHandler implements ResponseErrorHandler {

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

@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
    HttpStatus statusCode = clientHttpResponse.getStatusCode();
    MediaType contentType = clientHttpResponse
        .getHeaders()
        .getContentType();
    Charset charset = contentType != null ? contentType.getCharset() : null;
    byte[] body = FileCopyUtils.copyToByteArray(clientHttpResponse.getBody());

    switch (statusCode.series()) {
        case CLIENT_ERROR:
            throw new HttpClientErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        case SERVER_ERROR:
            throw new HttpServerErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        default:
            throw new RestClientException("Unknown status code [" + statusCode + "]");
    }

}

private boolean hasError(HttpStatus statusCode) {
    return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
        statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}
Emmanuel Osimosu
  • 5,625
  • 2
  • 38
  • 39
1

There is no such class implementing ResponseErrorHandler in Spring framework, so I just declared a bean:

@Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplateBuilder()
                .errorHandler(new DefaultResponseErrorHandler() {
                    @Override
                    public void handleError(ClientHttpResponse response) throws IOException {
                        //do nothing
                    }
                })
                .build();
    }
RichardK
  • 3,228
  • 5
  • 32
  • 52
0

The best way to make a RestTemplate to work with 4XX/5XX errors without throwing exceptions I found is to create your own service, which uses RestTemplate :

public ResponseEntity<?> makeCall(CallData callData) {

    logger.debug("[makeCall][url]        " + callData.getUrl());
    logger.debug("[makeCall][httpMethod] " + callData.getHttpMethod());
    logger.debug("[makeCall][httpEntity] " + callData.getHttpEntity());
    logger.debug("[makeCall][class]      " + callData.getClazz());
    logger.debug("[makeCall][params]     " + callData.getQueryParams());
    ResponseEntity<?> result;
    try {
        result = restTemplate.exchange(callData.getUrl(), callData.getHttpMethod(), callData.getHttpEntity(),
                callData.getClazz(), callData.getQueryParams());
    } catch (RestClientResponseException e) {
        result = new ResponseEntity<String>(e.getResponseBodyAsString(), e.getResponseHeaders(), e.getRawStatusCode());
    }

    return result;
}

And in case of exception, simply catch it and create your own ResponseEntity.

This will allow you to work with the ResponseEntity object as excepted.

Gregzenegair
  • 361
  • 4
  • 7