Skip to content
mmazi edited this page Oct 13, 2014 · 12 revisions

Basic exception handling in rescu

As a rule of thumb, you should declare throws IOException on all service interface methods*. If you don't, everything will still work, but any I/O exceptions (and any other checked exceptions) that happen will be wrapped in UndeclaredThrowableExceptions, which will make them more difficult and error-prone to handle. Note that since you're communicating with an external server, I/O exception conditions (like no connection, server down etc.) may happen often and it will pay off to put some effort into handling them properly.

* Unless you're mapping HTTP errors to exceptions - see the following section:

Mapping HTTP errors to exceptions

Rescu provides a declarative way to map HTTP error responses to Java exceptions: when the REST server returns a response with an non-OK HTTP response code, you can have your proxy client throw an exception. If you use custom exceptions and the server provides a json dictionary in the error response body, rescu can populate the exception's fields with the json data.

A minimal example

Just add a catch HttpStatusException block to your method calls. The HttpStatusException will provide you with the HTTP status code and response body, if you need them.

try {
    myServiceProxy.callMethod(...);
} catch (HttpStatusException e) {
    log.warn("HTTP error code returned from server: " + e.getHttpStatusCode());
} catch (IOException e) {
    ...
}

A contrived example

Say your server, on unsuccessful authentication, returns HTTP status code 401 and the following json:

{"success":false, "msg":"Incorrect username or password."}

On a successful call, the json would be differently structured (we're not covering this here). Then you might create a custom exception type (unnecessary details omitted):

public class MyException extends RuntimeException {
      @JsonProperty("success") private Boolean success;
      @JsonProperty("msg")     private String msg;

      public Boolean getSuccess() { return success; }
      public String getMsg() { return msg; }
      @Override public String getMessage() { return msg; }
}

.. and declare your method like this:

public MyResult doSomething(...) throws IOException, MyException;

This is all you need to do. When the server returns a non-OK (non-2xx) HTTP response code, rescu will parse the response body into a MyException object and throw it so you can catch it where you call the doSomething method and handle it properly; you can access the success and msg properties on the exception.

try {
    MyResult result = myServiceProxy.doSomething(...);
} catch (MyException e) {
    log.warn("Error message returned from server: " + e.getMsg());
} catch (IOException e) {
    ...
}

Note that you should still declare IOException in the method signature (and handle it) for the same reasons as stated above.

Triggering exception parsing based on json content

By default, rescu will parse the response body as exception only if HTTP response code is not 2xx. Here's how to make rescu parse response as exception even on 200 response codes when the json response is not what you expect.

Your method must return a Jackson-annotated custom type. (You can't use this with methods that return arrays or collections.) What you need to do is create constructor with Jackson-annotated parameters for this type that checks if all the data you need is present, and throws an ExceptionalReturnContentException if not:

public class DummyTicker {
    private Long last;
    private Long volume;

    public DummyTicker(
          @JsonProperty("last") Long last,
          @JsonProperty("volume") Long volume
    ) {
        if (last == null || volume == null) {
            throw new ExceptionalReturnContentException("Last and volume required.");
        }
        this.last = last;
        this.volume = volume;
    }
}

When constructing the return object from response body, Jackson will call this constructor. If ExceptionalReturnContentException is thrown, rescu will detect it and re-parse the body as exception.

Real-life examples

For further examples, see the XChange project, eg. the ANXV2 service and its exception type.

Assumptions and limitations

  • Your custom exception must be assignable to RuntimeException.
  • A single Exception type is supported per method (ie. no mapping from error codes to different exception types).

Details

  • You can prevent rescu from throwing exceptions on non-2xx HTTP response codes by setting rescu.http.ignoreErrorCodes = true in rescu.properties; this will force rescu to ignore the HTTP response code when deciding whether to parse the response body as exception or as regular return type.
  • You don't need to provide a custom exception type. Omitting it may be useful eg. if the server returns no data in the error response body, or if you don't care about this data. In this case you can simply declare your method as throws HttpStatusIOException (this will provide you with the HTTP response status code and the HTTP response body), or even throws IOException in case you don't even care about the status code and response body.
  • If parsing of the response body into your custom exception fails for any reason, rescu will throw a HttpStatusIOException that will enable you to access the HTTP response code and body.
  • You can extend HttpStatusExceptionSupport when creating your custom exception class. This is an easy way to automatically get the HTTP status code in your exception as a property and it will be added to your exception message. As a rule of thumb, if you don't need your own exception class hierarchy, you should just extend HttpStatusExceptionSupport to gain this additional info for free.
Clone this wiki locally