RxJava custom exception handling/propagation in spring boot rest application - java

I am wondering what is the best with RxJava and spring REST API?
I have a simple REST service and in the repository, in case of an error, I'd like to propagate a specific custom error to the client. But I am not sure how to map different custom exceptions with RxJava.
Here is a call to the backend:
private Single<Customer> findCustomerById(long customerId) {
return Single.fromCallable(() -> getRestTemplate().getForObject(
MyBackendService.SEARCH_CUSTOMER_BY_ID.getUrl(),
Customer.class, customerId))
.onErrorResumeNext(ex -> Single.error(new BackendException(ex)));
}
My exception:
public class BackendException extends Exception {
public BackendException(String message) {
super(message);
}
public BackendException(Throwable cause) {
super(cause);
}
So the question is how to map/propagate with RxJava this BackendException to let's say NotFound (404) or InternalServerError (500)?

I've used exception libraries that have exceptions for each type of HTTP response that you care about and a canonical error class that can be put into the body of an HTTP Response and easily parsed by a REST client, i.e, something with a code and a message.
As for translating exceptions to different HTTP responses, that depends on the version of Spring and REST library you're using. There are various ways to do that documented here, here and here.
The fact that you're using RxJava doesn't matter so much in determining your approach. I have used similar onErrorResumeNext code to what you have in your example.

Use RxJava subscription mechanism to handle errors, use onErrorResumeNext to return values and not exceptions. I would do something like this:
//The web service call is just this
private Single<Customer> findCustomerById(long customerId) {
return Single.fromCallable(() ->
getRestTemplate().getForObject(MyBackendService.SEARCH_CUSTOMER_BY_ID.getUrl(),
Customer.class,
customerId));
}
...
//Then just manage the exception on the subscription
findCustomerById(long customerId)
.subscribe(customer -> {
//Write the success logic here
System.out.println(customer);
}, throwable -> {
//Manage the error, for example
throwable.printStackTrace();
});
Hope it helps...

Related

How to preserve the error message that is thrown during a 500 internal exception using restTemplate

I want to know what is best practice to preserve error messages when calling several micro services that is chained: I have an angular front end that calls a back end rest service which calls another rest service which calls another 3rd party service.
The 3rd party service is somewhat unreliable. And I want the response from that service to be propagated to my front end.
So to make it easier for the sake of demo’ing the problem.
I have a control class in downstream project (separate micro-service/application)
#RestController
#RequestMapping("/my-down-stream-service")
public class MyController {
#RequestMapping(value = "my-method")
public MyCustomResponse method1() {
//Some complex logic that catch exceptions and propogates a nice little message
throw new RuntimeException(“This is my exception that indicates what the response is to my 3rd party service”);
}
}
On the other micro-service calling the service above I have a restTemplate making the call to the above service
public MyResponse doIt() {
try {
restTemplate.postForEntity(“MyUrl…”, req, MyResponse.class);
} catch (final HttpStatusCodeException ex) {
//If I add a break point and inspect the exception here
}
}
I can see it is a 500 internal exception that gets send to the front end.
If I go and get the ex.getResponseBodyAsString() I get back a JSON map with the actual detail of the exception.
{
"timestamp": "2020-05-06T22:17:08.401+0200",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "This is my exception that indicates what the response is to my 3rd party service",
"path": "…"
}
And I can convert this into a map and get the message portion and construct a new exception and throw that
new ObjectMapper().readValue(ex.getResponseBodyAsString(), HashMap.class).get("message")
But this seems like a lot of work that needs to be implemented where ever I need this.
Is there a better way of doing this?
I also tried creating my own HttpStatus - Like a 550 with my "Own custom message". But you cannot set the message for the HttpStatus code dynamically aka at Runtime. Not even sure if this is the correct venture or path to go down.
My solution in the end based on Amit's suggestion
I finally ended up creating a custom class that extends springs ResponseEntityExceptionHandler. If this is on the class path of your springboot app it will intercept the exception before returning it from the controller. I also created my own exception. Reason being this way if I want my functionality to trigger I fire my own exception and everyone else can still follow the normal way. It can be changed at any time.
Also on the client side I had to cast the exception's getBody() JSON to my exception. But I didn't knew if it was my exception to start of with. So I also added some HTTP header. And on the client side I check if that header is present then I know the body is my exception and I could comfortable convert the JSON to my exception.
#ControllerAdvice
public class MyRestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {MyCustomException.class})
protected ResponseEntity<Object> handleConflict(final MyCustomException ex, final HttpServletResponse response) {
if (!response.containsHeader("MYTAG")) {
response.addHeader("EX_TYPE", "MYTAG");
}
//here you can go wild as to what type of or just the normal 500
//return ResponseEntity.status(ex.getHttpStatus()).body(ex); // 500 internal exception
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex);
}
}
If I were you, I would like to create a controller advice to handle all kind of exceptions. Then I would like to create a ErrorMessage class which will have custom errorCode, errorMessage fields as per requirements. From this controller advice, for any kind of exceptions occurred in application, it will create an instance of ErrorMessage with details like errorCode and errorMessage and wrap into ResponseEntity object (with HTTP status) and return to the other microservices.
At consumer end check the response status and act accordingly.
I think the answer you are looking for is creating an implementation of ExceptionMapper. The interface is designed to handle java exceptions that map to Response.
In your case, if the 3rd part throws an exception which is handled by the ExceptionMapper implementation you can access the error message and return that in the response.
public class ServiceExceptionMapper implements ExceptionMapper<ServiceException>
{
/**
* {#inheritDoc}
*/
#Override
public Response toResponse(ServiceException exception)
{
//grab the message from the exception and return it in the response
}

Resilience4J Circuit Breaker to kick-in on specific HTTP status code

I am aware we can use recordExceptions() while building CircuitBreakerConfig to register exceptions on which Circuit Breaker should transition to OPEN state.
Code
I am using resilience4j-feign to decorate my CircuitBreaker. Would be really helpful if you can point me to a code example.
Question
How to make the Circuit Breaker kick-in in case of a specific HTTP status code (e.g. on 503 Service Unavailable) ?
You need to write an Exception/Response handler to your client's external calls and throw custom exceptions based on http status received. Then register these exceptions as record exceptions in your circuit breaker config. Following is a small example. The CB will be open only on AbcException. The CB config isresilience4j.circuitbreaker.instances.bookService.record-exceptions=com.sk.example.cb.circuitbreakerr4j.AbcException
#Service
#Slf4j
public class BookApiService {
RestTemplate restTemplate = new RestTemplate();
#CircuitBreaker(name = "bookService", fallbackMethod = "getBookFallback")
public String getBook(){
try {
ResponseEntity<String> stringResponseEntity = restTemplate.getForEntity(new URI("http://localhost:8080/book"), String.class);
if(null != stringResponseEntity){
if(stringResponseEntity.getStatusCode().is2xxSuccessful()){
return stringResponseEntity.getBody();
}
}
} catch (URISyntaxException e) {
e.printStackTrace();
}catch (HttpServerErrorException e){
log.error("Service unavailable",e);
if(e.getMessage().startsWith("503")) {
throw new AbcException();
}else{
throw e;
}
}
return "";
}
From the docs, Create and configure a CircuitBreaker:
// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordExceptions(IOException.class, TimeoutException.class) // add you exceptions here!!!
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.build();
A list of exceptions that are recorded as a failure and thus increase
the failure rate. Any exception matching or inheriting from one of the
list counts as a failure, unless explicitly ignored via
ignoreExceptions.
TL;DR: use a custom exception that communicates HTTP status (e.g. 503) from HTTP client (e.g. Feign) to Resilience4J
Feign: implement and configure an ErrorDecoder to throw a custom exception on HTTP status like 503
Resilience4J: record that custom exception using Circuit Breaker config.
Feign
Feign by default throws a FeignException in case of an erroneous HTTP status code. You can get the status code number via method int status().
To customize your feign-clients error-handling configure a (custom) implementation of ErrorDecoder
If you need more control over handling unexpected responses, Feign instances can register a custom ErrorDecoder via the builder.
[..]
All responses that result in an HTTP status not in the 2xx range will trigger the ErrorDecoder's decode method, allowing you to handle the response, wrap the failure into a custom exception or perform any additional processing. If you want to retry the request again, throw a RetryableException. This will invoke the registered Retryer.
Customize Feign error-handling
Implement and configure a custom ErrorDecoder to throw an exception in case of HTTP status 503.
#Component
#Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 400:
log.error("Status code {} on methodKey '{}'", response.status(), methodKey);
case 503:
return new ServiceUnavailableException("HTTP status 503 when calling " methodKey);
default:
return new Exception(response.reason());
}
}
}
This will then throw your custom exception ServiceUnavailableException.
Resilienc4J's CircuitBreaker
By default the circuit-breaker reacts on exceptions. It records them and will open the circuit if there are too much in too less time.
You can configure, which Exceptions to record and which to ignore as expected on the business-level.
Trigger CircuitBreaker on specific exceptions
You can configure CiruitBreaker to record that exception. Joke's answer explains how to do that.
See also
spring feign client exception handling
Feign Client Error Handling
Apps Developer Blog: Feign Error Handling with ErrorDecoder
Resilience4J docs: Feign, Decorating Feign Interfaces

HTTP Response Exception Handling in Spring 5 Reactive

I'm developing some reactive microservices using Spring Boot 2 and Spring 5 with WebFlux reactive starter.
I'm facing the following problem: I want to handle all HTTP Statuses that I receive from calling another REST Services and throws an exception when I receive some bad HTTP Status. For example, when I call an endpoint and I receive an 404 HTTP Status, I want to throw an exception and that exception to be handled in some ExceptionHandler class, just like the way it was in Spring 4 with #ControllerAdvice.
What is the right way to do this? Hope to receive some good suggestions.
This can be addressed in two independent parts.
How to convert HTTP 404 responses received by WebClient into custom exceptions
When using WebClient, you can receive HTTP 404 responses from remote services. By default, all 4xx and 5xx client responses will be turned into WebClientResponseException. So you can directly handle those exceptions in your WebFlux app.
If you'd like to turn only 404 responses into custom exceptions, you can do the following:
WebClient webClient = //...
webClient.get().uri("/persons/1")
.retrieve()
.onStatus(httpStatus -> HttpStatus.NOT_FOUND.equals(httpStatus),
clientResponse -> Mono.error(new MyCustomException()))
.bodyToMono(...);
This is obviously done on a per client call basis.
You can achieve the same in a more reusable way with an ExchangeFilterFunction that you can set once and for all on a WebClient instance like this:
WebClient.builder().filter(myExchangeFilterFunction)...
How to handle custom exceptions in WebFlux apps
With Spring WebFlux with annotations, you can handle exceptions with methods annotated with #ExceptionHandler (see Spring Framework reference documentation).
Note: using a WebExceptionHandler is possible, but it's quite low level as you'll have no high-level support there: you'll need to manually write the response with buffers without any support for serialization.
I think what you are looking for is WebFluxResponseStatusExceptionHandler the check this for reference.
In the WebHandler API, a WebExceptionHandler can be used to to handle
exceptions from the chain of WebFilter's and the target WebHandler.
When using the WebFlux Config, registering a WebExceptionHandler is as
simple as declaring it as a Spring bean, and optionally expressing
precedence via #Order on the bean declaration or by implementing
Ordered.
This example may help, have not tried it myself.
#Component
#Order(-2)
class RestWebExceptionHandler implements WebExceptionHandler{
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof PostNotFoundException) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
// marks the response as complete and forbids writing to it
return exchange.getResponse().setComplete();
}
return Mono.error(ex);
}
}
class PostNotFoundException extends RuntimeException {
PostNotFoundException(String id) {
super("Post:" + id + " is not found.");
}
}

Jersey: making the client to throw the same server exception

I understand that a Jersey-based web service is able to associate exceptions thrown by service methods to desired HTTP return codes (here). Now, is there any chance to make the client generate exactly the same exception that was generated by the service method? I mean, if the server side throws MySpecificException, is there a way to store such information (i.e., the FQN of the exception class) in the HTTP response (automatically, I don't want to turn to methods that build the response explicitly, I want them to return POJOs or void), so that the client can use it to re-throw the same exception?
REST does not specify exception as a response and thus there's no straightforward way to do this (this is not RPC).
However, you can introduce your own convention. For example:
On the provider side you could define ForbiddenException:
public class ForbiddenException extends WebApplicationException {
public ForbiddenException(String code, String readableMessage) {
super(Response.status(Status.FORBIDDEN).entity(new ForbiddenEntity(code, readableMessage)).build());
}
}
(You should probably compose response in ExceptionMapper instead of exception itself, but this is just for demonstration purposes).
And on the consumer side - ClientFilter:
public class ForbiddenExceptionClientFilter extends ClientFilter {
#Override
public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
ClientResponse response = getNext().handle(cr);
if (response.getStatus() == Status.FORBIDDEN.getStatusCode()) {
ForbiddenEntity reason = response.getEntity(ForbiddenEntity.class);
throw new RemoteResourceForbiddenException(reason.getCode(), reason.getReadableMessage());
}
return response;
}
}
This works as long as server complies with the convention and client uses the client filter.
Please note, this is not "exactly the same" exception - stacktrace is not transferred, however this is the right thing to do as it does not make any sense in client application. If you need stacktrace - it should be printed to logs using ExceptionMapper on server side.

Spring 3 controller exception handler implementation problems

I was hoping to implement a single "ExceptionController" to handle exceptions that are thrown in execution of my other controllers' methods. I hadn't specified any HandlerExceptionResolver in my application context so according to the API documentation the AnnotationMethodHandlerExceptionResolver should be started. I verified it as such in the source. So why doesn't the following work?
#Controller
public class ExceptionController {
#ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex) {
// Do some stuff
log.error(logging stuff)
return myModelAndView;
}
}
#Controller
public class AnotherController {
#RequestMapping(value="/nullpointerpath")
public String throwNullPointer() {
throw new NullPointerException();
}
}
I see in the debug logs that the three default exception handlers are asked for handling of the exception, but nothing is done and I see "DispatcherServlet - Could not complete request". Followed by the user being displayed the stacktrace and a 500 Internal error.
Make sure your Exception handler is returning a view that exists/maps to a handler.
You should write your exceptionhandler to the same class with which you want to handle, like the following.
#Controller
public class AnotherController {
#ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(NullPointerException ex) {
// Do some stuff.
log.error(logging stuff)
return myModelAndView;
}
#RequestMapping(value="/nullpointerpath")
public String throwNullPointer() {
throw new NullPointerException();
}
}
I don't think this is a good design. Controllers in Spring handle HTTP requests and map to URLs. I don't think "exception" fits into either bin. It feels like a misuse of Spring to me.
An exception is not an HTTP request. You don't map an exception to a URL. Therefore I'd conclude that controllers aren't intended to be treated as exception handlers.
Controllers are a part of the Spring API, but your design isn't using them as intended, so that's why it's not working. Re-think your design.

Categories