-
Notifications
You must be signed in to change notification settings - Fork 60
Exception handling
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 UndeclaredThrowableException
s, 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:
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.
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) {
...
}
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.
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.
For further examples, see the XChange project, eg. the ANXV2 service and its exception type.
- 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).
- You can prevent rescu from throwing exceptions on non-2xx HTTP response codes by setting
rescu.http.ignoreErrorCodes = true
inrescu.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 eventhrows 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 extendHttpStatusExceptionSupport
to gain this additional info for free.