8

I am using some REST endpoints from an external API and I am using the Rest Template interface for this purpose. I would like to be able to throw custom app exceptions when I receive certain HTTP status codes from these calls. In order to achieve it, I am implementing the ResponseErrorHandler interface as follows:

public class MyCustomResponseErrorHandler implements ResponseErrorHandler {

    private ResponseErrorHandler myErrorHandler = new DefaultResponseErrorHandler();

    public boolean hasError(ClientHttpResponse response) throws IOException {
        return myErrorHandler.hasError(response);
    }

    public void handleError(ClientHttpResponse response) throws IOException {
        String body = IOUtils.toString(response.getBody());
        MyCustomException exception = new MyCustomException(response.getStatusCode(), body, body);
        throw exception;
    }

}

public class MyCustomException extends IOException {

    private HttpStatus statusCode;

    private String body;

    public MyCustomException(String msg) {
        super(msg);
        // TODO Auto-generated constructor stub
    }

    public MyCustomException(HttpStatus statusCode, String body, String msg) {
        super(msg);
        this.statusCode = statusCode;
        this.body = body;
    }

    public HttpStatus getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(HttpStatus statusCode) {
        this.statusCode = statusCode;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

}

Finally, this is the client code (irrelevant code omitted):

public LoginResponse doLogin(String email, String password) {
    HttpEntity<?> requestEntity = new HttpEntity<Object>(crateBasicAuthHeaders(email,password));
    try{
        ResponseEntity<LoginResponse> responseEntity = restTemplate.exchange(myBaseURL + "/user/account/" + email, HttpMethod.GET, requestEntity, LoginResponse.class);
        return responseEntity.getBody();
    } catch (Exception e) {
        //Custom error handler in action, here we're supposed to receive a MyCustomException
        if (e instanceof MyCustomException){
            MyCustomException exception = (MyCustomException) e;
            logger.info("An error occurred while calling api/user/account API endpoint: " + e.getMessage());
        } else {
             logger.info("An error occurred while trying to parse Login Response JSON object");
        }
    }
    return null;
}

My app context:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:spring="http://camel.apache.org/schema/spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">

    <!-- Rest template (used in bridge communication) -->
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
        <property name="errorHandler" ref="myCustomResponseErrorHandler"></property>
    </bean>

    <!-- Bridge service -->
    <bean id="myBridgeService" class="a.b.c.d.service.impl.MyBridgeServiceImpl"/>

    <!-- Bridge error handler -->
    <bean id="myCustomResponseErrorHandler" class="a.b.c.d.service.handlers.MyCustomResponseErrorHandler"/>

</beans>

I suspect I am not correctly understanding the behaviour of this custom error handling. Every single rest template method might throw a RestClientException, which following the exception hierarchy, is a subclass of RuntimeException and not IOException, which is thrown in the custom response error handler, i.e.: I can't catch my custom exception in the rest template method calls.

Any clues on how I could catch these exceptions? Spring RestTemplate invoking webservice with errors and analyze status code is highly related but from my point of view experiences the same problem, although it was proposed as a solution.

[1]:

Community
  • 1
  • 1
jarandaf
  • 4,297
  • 6
  • 38
  • 67

2 Answers2

14

You've made your custom Exception extend from IOException

public class MyCustomException extends IOException {

The ResponseErrorHandler#handleError() method is invoked from RestTemplate#handleResponseError(..) which is invoked by RestTemplate#doExecute(..). This root invocation is wrapped in a try-catch block which catches IOException and rethrows it wrapped in a ResourceAccessException, which is a RestClientException.

One possibility is to catch the RestClientException and get its cause.

Another possibility is to make your custom Exception be a sub type of RuntimeException.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • What is the best approach here? What if my Exception must inherit from Exception, I would have to get the Exception behin RestClientException everytime when using RestTemplate – Zarathustra Mar 31 '15 at 10:11
  • @Zarathustra I don't think you can have another type of checked exception. `handleError` is only declared to throw `IOException`. Unless you're talking about some other component, those are the only two options, `IOException` or a unchecked exception. – Sotirios Delimanolis Mar 31 '15 at 14:34
0

You can create a controller using annotation @ControllerAdvice if you using springmvc. In the controller write:

@ExceptionHandler(HttpClientErrorException.class)
public String handleXXException(HttpClientErrorException e) {
    log.error("log HttpClientErrorException: ", e);
    return "HttpClientErrorException_message";
}

@ExceptionHandler(HttpServerErrorException.class)
public String handleXXException(HttpServerErrorException e) {
    log.error("log HttpServerErrorException: ", e);
    return "HttpServerErrorException_message";
}
...
// catch unknown error
@ExceptionHandler(Exception.class)
public String handleException(Exception e) {
    log.error("log unknown error", e);
    return "unknown_error_message";
}

and the DefaultResponseErrorHandler throw these two kinds of exceptions:

@Override
public void handleError(ClientHttpResponse response) throws IOException {
    HttpStatus statusCode = getHttpStatusCode(response);
    switch (statusCode.series()) {
        case CLIENT_ERROR:
            throw new HttpClientErrorException(statusCode, response.getStatusText(),
                    response.getHeaders(), getResponseBody(response), getCharset(response));
        case SERVER_ERROR:
            throw new HttpServerErrorException(statusCode, response.getStatusText(),
                    response.getHeaders(), getResponseBody(response), getCharset(response));
        default:
            throw new RestClientException("Unknown status code [" + statusCode + "]");
    }
}

and you can get using:e.getResponseBodyAsString();e.getStatusCode(); blabla to get response message when the exceptions occurs.

Qy Zuo
  • 2,622
  • 24
  • 21