Akki
Akki

Reputation: 774

Fallback Factory not working to handle Custom Exception in Feign Client

My requirement is to access the custom exception thrown from first service along with it's body content in the second service

I have tried 2 things so far, FallbackFactory and ErrorDecoder, out of which only Fallback factory worked for me. Error decoder did not have the message of the exception which was thrown from other service. Here is the sample code that I found in another question:

There will be 2 services: inventory-service and product-service

inventory-service

InventoryController.java

@RestController
@RequestMapping("/inventories")
public class InventoryController {

    private final ProductServiceClient productService;

    public InventoryController(ProductServiceClient productService) {
        super();
        this.productService = productService;
    }

    @GetMapping
    public ResponseEntity<?> companyInfo() {

        return productService.hello();

    }
}

ProductServiceClient.java

@FeignClient(name = "product-service", url = "http://localhost:9100", fallbackFactory = ProductServiceClientFallback.class)
public interface ProductServiceClient {

    @GetMapping("/products")
    ResponseEntity<?> hello();

}

@Component
class ProductServiceClientFallback implements FallbackFactory<ProductServiceClient> {

    @Override
    public ProductServiceClient create(Throwable cause) {

        return new ProductServiceClient() {

            @Override
            public ResponseEntity<?> hello() {
                System.out.println("hello!! fallback reason was " + cause.getMessage());
                return ResponseEntity.ok().build();
            }

        };
    }

}

product-service

ProductController.java

@RestController
@RequestMapping(value = "/products")
public class ProductController {

    @GetMapping
    public String hello() throws Exception {
        if (true) {
            throw new Exception("Service B Exception...");
        }
        return "Hello World";
    }
}

ProductControllerAdvice.java

@RestControllerAdvice
public class ProductControllerAdvice {
    @ExceptionHandler
    public ResponseEntity<?> handleException(Exception exception) {
        return new ResponseEntity<>("Caused due to : " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

So, when /inventories api is triggered in Inventory controller, it triggers a call to product-service via Feign Client and on product-service side, I throw a custom exception with a message, I have to access that message in my inventory-service.

To get that I have implemented fallback factory and it worked in a test-workspace since I got an output like this in console of inventory-service

hello!! fallback reason was status 500 reading ProductServiceClient#hello(); content:
Caused due to : Service B Exception...

But, my problem is when I try the similar approach with the applications that I'm working on, I did not get the message of exception, instead I got an out put like this:

reached fallback on workflow side, reason: status 400 reading ProvisioningServiceProxy#executeOrderAction(Long,Long,String)

Service-A

TestServiceA.java

@FeignClient( url = "/executeOrder", fallbackFactory = TestServiceAFallback.class )
public interface TestServiceA extends Serializable{
    @PostMapping( value = "order/{requestId}/order/{orderId}/{command}" )
    public ResponseEntity<ProcessInstanceVariable> executeOrderAction(                                                                            @PathVariable( name = "command" ) String command );
}

Service-B from where the custom exception is thrown

TestServiceBController.java

@PostMapping( value = /executeOrder )
public ResponseEntity<ProcessInstanceVariable> executeOrderAction(                                                                      @PathVariable( value = "command" ) String command )
{  //switch code to check the command value and throw exception for one particular command
          throw new ValidationException("validation exception from service B");
}

I have an advice also, which handles Validation Exceptions and there is a method like this in that class

TestServiceBControllerAdvice.java

@ExceptionHandler( ValidationException.class )
public ResponseEntity<Object> handleValidationException( ValidationException ve )
{
    return new ResponseEntity<>( ve.getMessage(), HttpStatus.BAD_REQUEST );
}

So, I was expecting to receive the message on TestServiceA side which I sent from TestServiceB, but I received a generic message showing that BAD REQUEST while reading the API.

I'm not sure if any extra configuration is required on TestServiceA side apart from below configuration:

testServiceA.properties

feign.hystrix.enabled=true

Let me know if anything is missing from my end, I have gone through this documentation and seems to me I have done the implementation the way it should happen to get the message and body of exception thrown from other service.

Upvotes: 4

Views: 5047

Answers (1)

Akki
Akki

Reputation: 774

For anyone who comes to this question looking for some answers, I did end up implementing ErrorDecoder, which helped me in capturing the errors. The details are a little fade to me, how the message was caught. But I used the below code:

public class CustomExceptionDecoder implements feign.codec.ErrorDecoder
{
    @Override
    public Exception decode( String methodKey,
                             Response response )
    {
        final ErrorDecoder defaultErrorDecoder = new Default();
        try
        {
            if( response.body() != null )
            {
                byte[] bodyData = Util.toByteArray( response.body().asInputStream() );
                String responseBody = new String( bodyData );
                LOGGER.error( "Error captured in Custom Exception Decoder: ", responseBody );
                return new CustomValidationException( responseBody );
            }
        }
        catch( IOException e )
        {
            LOGGER.error( "Throwing IOException :: {}", e.getCause() );
        }
        return defaultErrorDecoder.decode( methodKey, response );
    }
}

Upvotes: 1

Related Questions