Spring Feign client call enters exception block when it should stay in try block - spring

Need some small help about Spring Feign client. So here is the situation,
I have 2 Spring boot services. Let’s say Service A and Service B. I have configured my Service A with Feign client through which I call the Service B method.
So here is the code for my Service A,
My FeignCleint config interface,
#FeignClient(name = "FeignClient", url = "http://localhost:8081/ServiceB/hello")
public interface FeignApi {
#RequestMapping(method = RequestMethod.GET)
ResponseEntity<?> hello();
}
And my rest controller that uses above feign config to call the Service B method,
#RestController
public class ApiController {
#Autowired
private FeignApi feignApi;
#RequestMapping(value = "/callServiceB")
public ResponseEntity<?> companyInfo() {
ResponseEntity<?> response = new ResponseEntity("OK Response", HttpStatus.OK);
try {
response = feignApi.hello();
// Code for some other things related to application.
return response;
} catch (Exception ex) {
System.out.println("Service A Exception block reached.");
return new ResponseEntity(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
}
}
below is my controller for Service B,
#RestController
public class MyController {
#GetMapping("/hello")
public String hello() throws Exception {
if (true) {
throw new Exception("Service B Exception...");
}
return "Hello World";
}
}
And my Controller advice to handle the exception that I am manually throwing,
#ControllerAdvice
public class MyControllerAdvice {
#ExceptionHandler
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<?> handleException(Exception exception, Model model) {
return new ResponseEntity<>("Caused due to : " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Now my flow is like below,
As you can see, I am calling service B from service A using feign client. My service B is throwing an exception manually which I am catching using the controller advice and sending the exception details as an ResponseEntity back to the calling service A. So that Service A can process the details and move forward based on that.
The problem is when I hit the call from Service A using
http://localhost:8080/feign/callServiceB
The service B fails as expected. Now what I expect is that the Service A should receive the response back in form of the ResponseEntity. But what really happens is that the flow enters the exception block instead of staying in the try block. I can see this line printed,
"Service A Exception block reached."
This is what I don't understand. If I have managed the service B exception using controller advice and sent back the response to service A in form of ResponseEntity, then how come the flow of service A enters catch block. I expect it to stay in try block only as I want to process further based on the data.
Any idea, how can I get around this thing? Or is this how it will behave even when I am using controller advice to manage exceptions? What should be the expected behavior in this case?
Please advice.

By default Feign throws FeignException for any error situation.
Make use of fallback mechanism to handle failures.
#FeignClient(name = "FeignClient", url = "http://localhost:8081/ServiceB/hello", fallback= FeignApiFallback.class)
public interface FeignApi {
#RequestMapping(method = RequestMethod.GET)
ResponseEntity<?> hello();
}
#Component
class FeignApiFallback implements FeignApi {
#Override
public ResponseEntity<?> hello() {
//do more logic here
return ResponseEntity.ok().build();
}
}
make sure you add below property to wrap methods in hystrix commands in recent releases
feign.hystrix.enabled=true

Any status other than 200, feign client will consider it as an exception and you are setting HttpStatus.INTERNAL_SERVER_ERROR in your controller advice.
You can use custom ErrorDecoder
refer https://github.com/OpenFeign/feign/wiki/Custom-error-handling

Related

CircuitBreaker Fallback method not working

I have the below code in Billing service microservice:
#RestController
#RequestMapping("/billing")
public class WebController {
#Autowired
private BillingService service;
#GetMapping("/hi")
#CircuitBreaker(name="BillingServiceCapture", fallbackMethod = "hiFallback")
public String hi() {
return "Hello Khushboo!";
}
public String hiFallback() {
return "Hello Khushboo FallBack!";
}
Application.Properties file:
server.port=9191
spring.h2.console.enable=true
spring.application.name=billing-service
eureka.client.serviceurl.defaultzone=http://localhost:8761/eureka
eureka.instance.hostname=localhost
management.health.circuitbreakers.enabled=true
#actuator settings
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
resilience4j.circuitbreaker.instances.BillingServiceCapture.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.BillingServiceCapture.eventConsumerBufferSize=10
resilience4j.circuitbreaker.instances.BillingServiceCapture.failureRateThreshold=20
resilience4j.circuitbreaker.instances.BillingServiceCapture.minimumNumberOfCalls=5
resilience4j.circuitbreaker.instances.BillingServiceCapture.automaticTransitionFromOpenToHalfOpenEnabled=true
resilience4j.circuitbreaker.instances.BillingServiceCapture.waitDurationInOpenState=5s
resilience4j.circuitbreaker.instances.BillingServiceCapture.permittedNumberOfCallsInHalfOpenState=3
resilience4j.circuitbreaker.instances.BillingServiceCapture.slidingWindowSize=10
resilience4j.circuitbreaker.instances.BillingServiceCapture.slidingWindowType=COUNT_BASED
However, if I send a Get Request: localhost:8765/billing/hi
I get Hello Khushboo message.
But when I stop the BillingService microservice and again send the same request, the circuit breaker method doesn't get invoked.
Also, while accessing the Actuator Health status, I do not see circuit breaker information in the status logs which I should see.
I even added the CircuitBreaker code in OrderService which actually calls the BillingService:
#CircuitBreaker(name="BillingServiceCapture", fallbackMethod = "getAllBillingDetails")
public TransactionResponse saveOrder(TransactionRequest request) {
Order order=request.getOrder();
Billing billing=request.getBilling();
billing.setOrderId(order.getId());
billing.setAmount(order.getPrice());
Order ordermade=orderRepo.save(order);
Billing billingresponse=billingproxy.getBillingDone(billing);
TransactionResponse response=null;
String responseStr= billingresponse.getPaymentStatus().equals("success")?"Payment processing successful":"Payment failed";
response=new TransactionResponse(order, billingresponse.getTransactionId(),billingresponse.getAmount(),responseStr);
return response;
}
public Billing getAllBillingDetails(Billing bill,Exception e) {
return new Billing(1000,"pass",101,102,1000);
}
When I call http://localhost:8765/order/bookorder - this throws a 500 internal server exception but CircuitBreaker is not called. The error is:
[503] during [GET] to [http://billing-service/billing/preparebill] [BillingProxy#getBillingDone(Billing)]: [Load balancer does not contain an instance for the service billing-service]
Please note for testing purpose I'm not starting BillingService so the instance is not available for OrderService Feign to call.
Any insights will be appreciated.
Thanks.
The fallback method should pass the Exception parameter and return the same type as the original method:
public String hiFallback(Exception e) {
return "Hello Khushboo FallBack!";
}

How to send error message with restfulAPI using SpringBoot

I have server which is built using SpringBoot and restful api's. It is simple CRUD application.
I am trying to check if email already exists while adding a new user.
I am not sure how to send error messages over rest api. I have tried like this:
UserController.java
//POST method for adding one user
#PostMapping("/addUser")
public ResponseEntity<User> addUser(#RequestBody User user){
User existingUser = userRepository.findByEmail(user.getEmail());
if(existingUser != null){
throw new UserAlreadyExistException("User with this email already exists");
}
return new ResponseEntity<>(service.saveUser(user), HttpStatus.OK) ;
}
UserAlreadyExistException.java
public class UserAlreadyExistException extends RuntimeException{
public UserAlreadyExistException(String message) {
super(message);
}
}
When I test it with Postman I get Error: 500 Internal Server Error
And in InteliJ I have this exception thrown:
com.example.library.UserAlreadyExistException: User with this email
already exists
Is this a correct way to do it or what is the best practice?
What you're looking for is #ControllerAdvice and #ExceptionHandler. The way such exceptions are handled is:
Create a global exception handler and annotate with #ControllerAdvice
Catch your exception using #ExceptionHandler in one of the methods specific to a certain exception.
So, adding below code will catch the exception and return custom response.
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public ResponseEntity<Object> handleAuthenticationException(UserAlreadyExistException e) {
// do what you want with e
return new ResponseEntity<>("User already exists", HttpStatus.OK);
}

How to handle exceptions thrown in the service layer?

I'm working on a spring-boot application. I tried handling exceptions .But i guess there is something wrong about how I'm doing it because it always throws internal server error 500.
I tried setting up custom exception classes and also used response status codes with #ResponseStatus. But regardless of what the exception is it throws an internal server error only.
I'm using intellij and the message i've given in the exception is printed there but the response body is empty.This i guess must be because it is throwing an internal server error.
Controller class
#RequestMapping(value = "/attendance",method = RequestMethod.POST)
public ResponseEntity<?> enterAttendance(#RequestBody ViewDTO viewDTO) throws CustomException{
return new ResponseEntity<>(tempResultServices.handleAttendance(viewDTO),HttpStatus.OK);
}
}
Service layer
#Override
public TempResult handleAttendance(ViewDTO viewDTO) throws CustomException {
TempIdentity tempIdentity=new TempIdentity();
tempIdentity.setRegistrationNo(viewDTO.getRegistrationNo());
tempIdentity.setCourseId(viewDTO.getCourseId());
tempIdentity.setYear(viewDTO.getYear());
tempIdentity.setSemester(viewDTO.getSemester());
User user=userService.findByUserId(viewDTO.getUserId());
tempIdentity.setUser(user);
if(!viewDTO.isAttendance()){
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance(),0);
ResultIdentity resultIdentity=new ResultIdentity(tempIdentity.getRegistrationNo(),tempIdentity.getCourseId(),tempIdentity.getYear(),tempIdentity.getSemester());
Result result=new Result(resultIdentity,0,"E*");
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
resultRepository.save(result);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
else{
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance());
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
}
The exception class
#ResponseStatus(code= HttpStatus.NOT_FOUND)
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
The terminal in the intellij prints "No draft available ". But i want it not as an internal server error.
Can some one tell me how i should be handling these errors please?
I tried using the #RestControllerAdvice
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomException.class)
public ResponseMsg handleNotFoundException(CustomException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
And this is my response message class
public class ResponseMsg {
private String message;
//getters and setters
}
This is another simple request in the application
#RequestMapping(value = "/user/view",method = RequestMethod.POST)
public ResponseEntity<?> getUser(#RequestBody UserDTO userDTO) throws CustomException{
User user=userService.findByUsername(userDTO.getUsername());
if(user!=null){
return ResponseEntity.ok(user);
}
//
throw new CustomException("User not found");
}
But still the custom exception is not thrown. The response body is empty. but intellij says "user not found" and postman returns the status code 500.
Spring boot has a very convenient way to handle exceptions in any layer of your application which is defining a #ControllerAdvice bean. Then you can throw any type of exception in your code and it will be "captured" on this class.
After this you can handle and return whatever your app needs to return.
By the way, you can return your custom object and it will be parsed to json automatically.
Documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Sample code:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Object processValidationError(BadRequestException ex) {
//return whatever you need to return in your API
}
}

Spring controller advice does not correctly handle a CompletableFuture completed exceptionally

I am using Spring Boot 1.5, and I have a controller that executes asynchronously, returning a CompletableFuture<User>.
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private final UserService service;
#GetMapping("/{id}/address")
public CompletableFuture<Address> getAddress(#PathVariable String id) {
return service.findById(id).thenApply(User::getAddress);
}
}
The method UserService.findById can throw a UserNotFoundException. So, I develop dedicated controller advice.
#ControllerAdvice(assignableTypes = UserController .class)
public class UserExceptionAdvice {
#ExceptionHandler(UserNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public String handleUserNotFoundException(UserNotFoundException ex) {
return ex.getMessage();
}
}
The problem is that tests are not passing returning an HTTP 500 status and not a 404 status in case of an unknown user request to the controller.
What's going on?
The problem is due to how a completed exceptionally CompletableFuture handles the exception in subsequent stages.
As stated in the CompletableFuture javadoc
[..] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. [..]
In my case, the thenApply method creates a new instance of CompletionStage that wraps with a CompletionException the original UserNotFoundException :(
Sadly, the controller advice does not perform any unwrapping operation. Zalando developers also found this problem: Async CompletableFuture append errors
So, it seems to be not a good idea to use CompletableFuture and controller advice to implement asynchronous controllers in Spring.
A partial solution is to remap a CompletableFuture<T> to a DeferredResult<T>. In this blog, an implementation of a possible Adapter was given.
public class DeferredResults {
private DeferredResults() {}
public static <T> DeferredResult<T> from(final CompletableFuture<T> future) {
final DeferredResult<T> deferred = new DeferredResult<>();
future.thenAccept(deferred::setResult);
future.exceptionally(ex -> {
if (ex instanceof CompletionException) {
deferred.setErrorResult(ex.getCause());
} else {
deferred.setErrorResult(ex);
}
return null;
});
return deferred;
}
}
So, my original controller would change to the following.
#GetMapping("/{id}/address")
public DeferredResult<Address> getAddress(#PathVariable String id) {
return DeferredResults.from(service.findById(id).thenApply(User::getAddress));
}
I cannot understand why Spring natively supports CompletableFuture as return values of a controller, but it does not handle correctly in controller advice classes.
Hope it helps.
For those of you who still run into trouble with this : even though Spring correctly unwraps the ExecutionException, it doesn't work if you have a handler for the type "Exception", which gets chosen to handle ExecutionException, and not the handler for the underlying cause.
The solution : create a second ControllerAdvice with the "Exception" handler, and put #Order(Ordered.HIGHEST_PRECEDENCE) on your regular handler. That way, your regular handler will go first, and your second ControllerAdvice will act as a catch all.

Server sends INTERNAL_SERVER_ERROR, but client receives OK

I've got a method on the server which returns HttpStatus.INTERNAL_SERVER_ERROR but the client application always gets OK http status.
This is servers method (It's implified):
#RequestMapping(value="/example", method = RequestMethod.POST)
HttpStatus createSomething(Principal principal, #RequestBody #Valid SomeObject so) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
I'm sure that the right request is being made by cient application. Any ideas what migh cause the problem?
It won't work like that you should either:
return new ResponseEntity<String>(HttpStatus. INTERNAL_SERVER_ERROR)
or throw some custom exception with http status:
ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerException extends RuntimeException {
public InternalServerException (String message) {
super(message);
}
}

Resources