Feign Client not catching Exception code 503 service unavailable - spring-boot

I am calling service B from Service A through open-feign. I have already implemented retryer and Error Decoder to retry the call when the calling service is unavailable. But When I forcefully down the instace of serviceB to test my retry changes feign is not getting 503 error code to retry but it is giving me the general exception with exception "Load Balancer does not have available server for serviceB" with status code 0.
Feign Config
#Configuration
#EnableFeignClients(basePackages = "com.microservices.servicea")
public class FeignConfig {
#Value("${feign.client.config.default.connectTimeout:60000}")
private int connectTimeout;
#Value("${feign.client.config.default.readTimeout:60000}")
private int readTimeout;
#Value("${feign.client.config.default.retryDelay}")
private long retryDelay;
#Value("${feign.client.config.default.maxRetryDelay}")
private long maxRetryDelay;
#Value("${feign.client.config.default.maxAttempts}")
private int maxAttempts;
#Bean
public ErrorDecoder feignErrorDecoder() {
return new FeignErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default(retryDelay, maxRetryDelay, maxAttempts);
}
}
Error Decoder
public class FeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public RuntimeException decode(final String methodKey, final Response response) {
final int errorCode = response.status();
String errorReason = response.reason();
final Exception exception = defaultErrorDecoder.decode(methodKey, response);
if (errorCode === 503) {
return new RetryableException("retry reason", response.request().httpMethod(),
null);
}
}
}
Feign Client of ServiceB
#Component
#FeignClient("serviceB")
public interface serviceBClient {
#GetMapping("/users/{id}")
Resource<User> findById(#PathVariable("id") String id);
}
When i call serviceBClient.findById(someId) and the serviceB instance should give me feign exception of "Service Unavailable" wih status code 503. But it is giving me general exception "Load Balance does not have available server " with 0 status code.
Also when i call the same api of ServiceB thorugh postman. I am getting the actual response on Postman i.e Service unavailable with statuscode 503.

Related

error handling with reactiveFeignClient and CircuitBreaker

we are using reactive feign client (com.playtika.reactivefeign:feign-reactor-spring-cloud-starter:3.2.0)
circuit breaker version : org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j:2.1.0
and spring boot application version org.springframework.boot’ version ’2.6.6
when we get an error from reactive feign client (such as 404 error)
#ReactiveFeignClient(name = "someRestClient", url = "${react-gpi-service.url}",configuration = AuthConfigurationsomeRestClient.class, fallbackFactory = someRestClienttFallbackFactory.class)
#Profile("!test")
public interface someRestClient {
#PostMapping(value = "/v2/{entity}/any", produces = MediaType.ALL_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
Mono<String> any(#PathVariable(value = "entity")
it goes over the error decoder to check if it should be retried
#Slf4j
#RequiredArgsConstructor
public class RetryableErrorDecoder implements ErrorDecoder {
private static ErrorDecoder defaultErrorDecoder = new Default();
private final String clientName;
public Exception decode(String methodKey, Response response) {
String body = "";
try {
body = IOUtils.toString(response.body().asInputStream(), StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("failed to parse error response body", e);
}
log.error("In RetryableErrorDecoder, got an error from {}. status: {}, body: {}, reason: {}, request: {}",
clientName, response.status(), body, response.reason(), response.request());
if (response.status() == HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE ||
response.status() == HttpStatusCodes.STATUS_CODE_BAD_GATEWAY) {
log.warn("Retry on error 503 or 502");
return createRetryableException(response, "Service Unavailable 503, 502");
} else {
Exception decode = defaultErrorDecoder.decode(methodKey, response);
if (decode instanceof FeignException &&
decode.getMessage().contains("authorizing")) {
log.warn("Retry on {}", decode.getMessage());
return createRetryableException(response, "Service authorizing problem");
}
return decode;
}
}
private Exception createRetryableException(Response response, String message) {
return new RetryableException(
response.status(),
message,
response.request().httpMethod(),
null,
null,
response.request());
}
}
after that it goes to Circuit beaker predicate
public class someFailurePredicate implements Predicate<Throwable> {
#Override
public boolean test(Throwable throwable) {
return throwable instanceof ThirdPartyException
|| throwable instanceof ReadTimeoutException
|| throwable instanceof OutOfRetriesException;
}
}
and then it goes to fallBackFactory mechanism because the circuit breaker requires the fallback method so the circuit breaker predicate is activated again.
#Component
public class someRestClientFallbackFactory implements FallbackFactory<someRestClient> {
#Override
public someRestClient apply(Throwable throwable) {
return new someRestClientFallback(throwable);
}
}
public class someRestClientFallback implements someRestClient {
private final Throwable cause;
public someClientFallback(Throwable cause) {
this.cause = cause;
}
public Mono<String> performSearchRequest(String entity,
) {
return Mono.error(cause);
}
}
because we have 2 mechanisms of error handling the circuit predicate is calling twice and duplicating the error.
I tried to move the retry mechanism(error decoder) to fallback method but the fallbackfactory method accepts throwable and reactiveFeignClientException doesn't have a status code and it's hard to determine if we should do the retry.
if I remove the fallback method I get this error message :
org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException: No fallback available.
we need to add it but then we have two mechanisms and a duplicate circuit breaker predicate count
Reactive Feign Client enables its own CB by default, it is possible to disable it by setting reactive.feign.circuit.breaker.enabled to false - https://github.com/PlaytikaOSS/feign-reactive/blob/develop/feign-reactor-spring-configuration/README.md

Spring boot #Retryable not working in service class

I'm trying to add retry logic in my application for sending mail to respective users through rest controller and i have annotate #EnableRetry in my SpringbootApplication class file
#RestController
public class TserviceController {
#Autowired
private Tservice tService ;
#RequestMapping(method = RequestMethod.GET, value = "/sendMail")
public Object sayHello(HttpServletResponse response) throws IOException {
try{
boolean t = tService.sendConfirmationMail();
}catch(Exception e){
System.out.println("--> rest failed");
return ResponseEntity.status(500).body("error");
}
return ResponseEntity.status(200).body("success");
}
}
My Tservice.class
#Service
public class Tservice {
private JavaMailSender javaMailSender;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
public Tservice(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
#Retryable(backoff = #Backoff(delay = 5000), maxAttempts = 3)
public boolean sendConfirmationMail() throws Exception {
try{
System.out.println("--> mail service calling");
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(toEmail);
mailMessage.setSubject(subject);
mailMessage.setText(message);
mailMessage.setFrom(emailFrom);
javaMailSender.send(mailMessage);
return true;
}catch(Exception e){
throw new Exception(e);
}
}
#Recover
public void recover(Exception ex) {
System.out.println("--> service failed");
}
}
When i try to run the /sendMail and whenever exception arise in service class it retrying 3 times successfully but after reaching the maxattempts, i'm getting the console print as below
--> mail service calling
--> mail service calling
--> mail service calling
--> rest failed
instead of printing
--> service failed
Here what im doing wrong..?
As per Javadoc for #Recover your recover method must have the same return type as the Retryable method.
So it should be
#Recover
public boolean recover(Exception ex) {
System.out.println("--> service failed");
return false;
}
JavaDoc:
A suitable recovery handler has a first parameter of type Throwable (or a subtype of Throwable) and a return value of the same type as the #Retryable method to recover from.

Is there a way in spring boot to manually invoke the Exception Advice?

I have a scenario where is an already existing controller and the service throws exceptions which are handled via the #RestControllerAdvice.
Now i have a new class which i have introduced which invokes methods from the above service class in a batch mode. In my class i have to capture the exceptions or successes bundle them up and return. For any exceptions that occur i need to report the HTTP Status and the error message.
Could you let me know if there is any way this can be achieved?
You can create your own Exception class.
public class MyException extends Exception {
private int errorCode;
private String errorMessage;
public MyException(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
and you can create new MyException when occurring any exception and throw it. Then you get this exception in the #RestControllerAdvice class.
#RestControllerAdvice
public class ExceptionAdvice {
private ErrorCodeMapper errorCodeMapper;
#Autowired
public ExceptionAdvice(ErrorCodeMapper errorCodeMapper) {
this.errorCodeMapper = errorCodeMapper;
}
#ExceptionHandler(value = MyException.class)
public ResponseEntity handleGenericNotFoundException(MyException e) {
return new ResponseEntity(errorCodeMapper.getStatusCode(e.getErrorCode()));
}
}
and mapper class like below:
#Service
public class ErrorCodeMapper {
public static Map<Integer,HttpStatus> errorCodeMap = new HashMap<>();
public ErrorCodeMapper(){
errorCodeMap.put(100, HttpStatus.BAD_REQUEST);
errorCodeMap.put(101,HttpStatus.OK);
errorCodeMap.put(102,HttpStatus.BAD_REQUEST);
errorCodeMap.put(103,HttpStatus.BAD_REQUEST);
}
HttpStatus getStatusCode(int errorCode){
return errorCodeMap.get(errorCode);
}
}
You can more details to MyException and add the error message to the ResponseEntity.

Spring #ControllerAdvice vs ErrorController

In my REST service app, I am planning to create a #ControllerAdvice class to catch controller thrown exceptions and return ResponseEntity objects according to the error type.
But I already have a #RestController class implementing the ErrorController interface to catch all exceptions.
Do these two interfere in any manner?
In which cases will ErrorController be called when #ControllerAdvice exists?
Edit:
The ErrorController code as requested
#RestController
public class ControllerCustomError implements ErrorController{
//error json object
public class ErrorJson {
public Integer status;
public String error;
public String message;
public String timeStamp;
public String trace;
public ErrorJson(int status, Map<String, Object> errorAttributes) {
this.status = status;
this.error = (String) errorAttributes.get("error");
this.message = (String) errorAttributes.get("message");
this.timeStamp = errorAttributes.get("timestamp").toString();
this.trace = (String) errorAttributes.get("trace");
}
}
private static final String PATH = "/error";
#Value("${hybus.error.stacktrace.include}")
private boolean includeStackTrace = false;
#Autowired
private ErrorAttributes errorAttributes;
#RequestMapping(value = PATH)
ErrorJson error(HttpServletRequest request, HttpServletResponse response) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
// Here we just define response body.
return new ErrorJson(response.getStatus(), getErrorAttributes(request, includeStackTrace));
}
#Override
public String getErrorPath() {
return PATH;
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
}
An implementation of the ErrorController is used to provide a custom whitelabel error page.
A class annotated with #ControllerAdvise is used to add a global exception handling logic for the whole application. Thus, more than one controller in your application.
If in your application there is no mapping found for a request or page then spring will fallback to the 'whitelabel error page'. And in this case it will be the custom implementation of ErrorController

Setting the status code message of a HTTP ResponseCode thrown by an #ResponseCode annotate Exception

I am currently trying to set the message of a HTTP Status Code thrown by an #ResponseCode annotated Exception.
I have defined the exception:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public final class BadRequestException extends IllegalArgumentException {
/**
* The serial version UID.
*/
private static final long serialVersionUID = -6121234578480284282L;
public BadRequestException() {
super();
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
public BadRequestException(String message) {
super(message);
}
public BadRequestException(Throwable cause) {
super(cause);
}
}
If the exception is thrown I throw it again in my #ExceptionHandler annotate method:
#ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(Exception e, HttpSession session) {
if (e instanceof BadRequestException) {
throw (BadRequestException)e;
}
return FAILURE;
}
I generally throw this exception in this way:
if (result.hasErrors()) {
throw new BadRequestException("Bad Request Message.");
}
The HTTP Status Code always returns only "HTTP Status 400 -" without a message is set.
THX!
Annotate your exception handler with the #ResponseStatus. Then created a basic error/exception view and pass the exception stack trace or whatever to that view

Resources