Handle exception after reaching max attempts in resilience4j-retry using Spring Boot - java

I have a scenario I want to log each retry attempt and when the last one fails (i.e. maxAttempts reached) a exception is thrown and let's say an entry to a database is created.
I try to achieve this using Resilience4j-retry with Spring Boot, therefore I use application.yml and annotations.
#Retry(name = "default", fallbackMethod="fallback")
#CircuitBreaker(name = "default", fallbackMethod="fallback")
public ResponseEntity<List<Person>> person() {
return restTemplate.exchange(...); // let's say this always throws 500
}
The fallback logs the cause of the exception into an application log.
public ResponseEntity<?> fallback(Exception e) {
var status = HttpStatus.INTERNAL_SERVER_ERROR;
var cause = "Something unknown";
if (e instanceof ResourceAccessException) {
var resourceAccessException = (ResourceAccessException) e;
if (e.getCause() instanceof ConnectTimeoutException) {
cause = "Connection timeout";
}
if (e.getCause() instanceof SocketTimeoutException) {
cause = "Read timeout";
}
} else if (e instanceof HttpServerErrorException) {
var httpServerErrorException = (HttpServerErrorException) e;
cause = "Server error";
} else if (e instanceof HttpClientErrorException) {
var httpClientErrorException = (HttpClientErrorException) e;
cause = "Client error";
} else if (e instanceof CallNotPermittedException) {
var callNotPermittedException = (CallNotPermittedException) e;
cause = "Open circuit breaker";
}
var message = String.format("%s caused fallback, caught exception %s",
cause, e.getMessage());
log.error(message); // application log entry
throw new MyRestException (message, e);
}
When I call this method person() the retry happens as maxAttempt configured. I expect my custom runtime MyRestException is caught on each retry and thrown on the last one (when maxAttempt is reached), so I wrap the call in the try-catch.
public List<Person> person() {
try {
return myRestService.person().getBody();
} catch (MyRestException ex) {
log.error("Here I am ready to log the issue into the database");
throw new ex;
}
}
Unfortunatelly, the retry seems to be ignored as the fallback encounters and rethrows the exception that is immediatelly caught with my try-catch instead of the Resilience4j-retry mechanism.
How to achieve the behavior when the maxAttempts is hit? Is there a way to define a specific fallback method for such case?

Why don't you catch and map exceptions to MyRestException inside of your Service methods, e.g. myRestService.person()?
It makes your configuration even simpler, because you only have to add MyRestException to the configuration of your RetryConfig and CircuitBreakerConfig.
Spring RestTemplate also has mechanisms to register a custom ResponseErrorHandler, if you don't want to add the boilerplate code to every Service method. -> https://www.baeldung.com/spring-rest-template-error-handling
I would not map CallNotPermittedException to MyRestException. You don't want to retry when the CircuitBreaker is open. Add CallNotPermittedException to the list of ignored exceptions in your RetryConfig.
I think you don't need the fallback mechanism at all. I thing mapping an exception to another exception is not a "fallback".

Related

How to handle a PSQLException in java?

I have a unique constraint on one of my entities and whenever I get a PSQLException which occurs whenever that constraint is violated, I want to respond with a bad request.
This is my exception handler which I tried to implement:
#ControllerAdvice
public class DatabaseExceptionHandler {
#ExceptionHandler(value = PSQLException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleDatabaseExceptions(PSQLException e) {
// i want to respond with a bad request only when this condition is satisfied
//
// if (e.getSQLState().equals("23505")) {
//
// }
}
}
And this is where the model is saved in db:
public DepartmentForHoliday setDepartment(DepartmentForHoliday department) {
if (department.getDepartmentId() == null) {
Department savedDepartment = new Department();
savedDepartment.setName(department.getName());
try {
departmentRepository.save(savedDepartment);
} catch (PSQLException e) {
/*here i have a compiler error which says that this exception is never thrown in the corresponding try block, but where ?*/
}
}
This is the exception that is thrown when I add a duplicate entry:
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "uk_1t68827l97cwyxo9r1u6t4p7d"
Detail: Key (name)=(Tech) already exists.
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2458) ~[postgresql-9.4.1211.jre7.jar:9.4.1211.jre7]
How to handle PSQLExceptions ? Should I make my own exception as a wrapper or how to solve this problem ?
Key problem is that PSQLException is wrapped into some Spring exception (which I assume from your code); you have to unwrap it (for example using guava's Throwables):
public DepartmentForHoliday setDepartment(DepartmentForHoliday department) {
if (department.getDepartmentId() == null) {
Department savedDepartment = new Department();
savedDepartment.setName(department.getName());
try {
departmentRepository.save(savedDepartment);
} catch (RuntimeException e) {
Throwable rootCause = com.google.common.base.Throwables.getRootCause(e);
if (rootCause instanceof SQLException) {
if ("23505".equals(((SQLException) rootCause).getSQLState())) {
// do smth interesting :)
}
}
}
}
}
Once you do that you can throw your custom exception and handle it in DatabaseExceptionHandler
You are catching PSQLException. Instead of that, please catch SQLException. With SQLException you will can handle all this SQL exceptions.
You can check the SQLException knowledge at this link
Then in your code just treat the SQLException as you want. The most generic catch clause is the following one:
catch (SQLException e)
{
System.out.println("ERROR: Fetch statement failed: " +
e.getMessage());
}
With this code you are printing the exception. If you want more information, check this
This is quite late, but building on previous responses I was able to solve it as so:
try {
return this.projectRepository.saveAndFlush(patchedProjectEntity);
} catch (DataIntegrityViolationException e) {
if (e.getMostSpecificCause().getClass().getName().equals("org.postgresql.util.PSQLException") && ((SQLException) e.getMostSpecificCause()).getSQLState().equals("23505"))
throw new UniqueConstraintViolationException("", e.getMostSpecificCause());
throw e;
}
Where UniqueConstraintViolationException is a custom exception and handled with a spring controller advice.
You might as well register an exception handler for that wrapped exception (that #radek mentioned) directly.
In your case that's:
#ExceptionHandler(DataIntegrityViolationException::class)
protected fun handleDataIntegrityException(ex: DataIntegrityViolationException, request: WebRequest) : ResponseEntity<SomeBody>{
return ResponseEntity.badRequest().body(someBodyHere)
}
The error is converted within convertHibernateAccessException in org.springframework.orm.jpa.vendorHibernateJpaDialect, which has already processed away from PSQL. You can add a breakpoint there and follow the stacktrace.
There is a lot of proxy'ing happening under the hood, but the takeaway is that there is always a readable, expressive Exception to use directly.

Can I throw a custom error if a hystrix-protected call times out?

I have a feign client with this external call:
#RequestMapping(method = RequestMethod.GET, value = "GetResourceA", consumes = "application/json")
#Cacheable("ResourceA")
List<Stop> getResourceA() throws MyOwnException;
And in my application.yml I have this setting:
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds: 1000
fallback.enabled: false
Now if getResourceA times out, i.e. it takes more than one second to complete, I either get this:
com.netflix.hystrix.exception.HystrixRuntimeException: getResourceA timed-out and no fallback available
Or, if I define a fallback from which I throw my own exception, I get this:
com.netflix.hystrix.exception.HystrixRuntimeException: getResourceA timed-out and fallback failed.
Can I not throw my own exception from the fallback?
What if I wish to throw my own exception when the service is down? I would like to not have a fallback (because I have no reasonable value to return from the fallback), but instead throw my own error that I can catch and let the program resume. Can someone help me out with this?
Update after the answer from Ben:
So I tried the approach with catching HysterixRuntimeException and checking what caused it, but ended up with this ugly code:
try {
getResourceA();
} catch (HystrixRuntimeException e) {
if (e.getFailureType().name().equals("TIMEOUT")) {
throw new MyOwnException("Service timed out");
}
throw e;
}
All that to be able to throw MyOwnException on a timeout. Surely there must be another way?
You should be able to get the exception you throw from your fallback by getting the cause of the HystrixRuntimeException
So, to handle your custom exception, you can do this:
try {
getResourceA();
} catch (HystrixRuntimeException e) {
if (e.getCause() instanceof MyException) {
handleException((MyException)e.getCause());
}
}
You can use an ErrorDecoder and return your own exception from there and then use an exception handler. I had a similar problem and solved it like this:
public class ExceptionHandlerControllerAdvice extends ResponseEntityExceptionHandler
...
#ResponseStatus(BAD_REQUEST)
#ExceptionHandler(HystrixRuntimeException.class)
public ExceptionResponse handleHystrixRuntimeException(HystrixRuntimeException exception) {
if(exception.getCause() instanceof MyException) {
return handleMyException((MyException) exception.getCause());
...
And then in my Configuration class for my FeignClient:
#Bean
public ErrorDecoder feignErrorDecoder() {
return new ErrorDecoder() {
#Override
public Exception decode(String methodKey, Response response) {
return new MyException("timeout");
}
};
}
That way you don't need multiple nested getCause()
If you want replace timeout exception throw by hystrix,you can write like this:
try {
testClient.timeoutTest();
} catch (HystrixRuntimeException e) {
Throwable fallbackException = e.getFallbackException();
if (fallbackException.getCause().getCause() instanceof CustomException) {
log.info("customer exception!");
}
}

Play Framework 2.2 (java) WS recovery block does not catch HTTP exception

(Seemingly) simple static method that invokes a known good web service.
Service returns HTTP 500 / Internal Server Error if a matching record is not found, but the recovery block of invokation is never executed.
Am I missing the obvious or doing something blatantly stupid?
public static Promise<Property> ByPhone(String phone) {
return WS.url("http://localhost:9000/data/property/" + phone)
.get ()
.map (
new Function<WS.Response, Property>() {
public Property apply (WS.Response response) {
System.out.println("got here: " + response.getStatusText());
Property property = null;
try {
property = _mapper.readValue(response.getBody(), Property.class);
} catch (Throwable t) {
t.printStackTrace();
}
return property;
}
}
).recover (
new Function<Throwable, Property>() {
public Property apply (Throwable t) {
System.out.println("never get here");
t.printStackTrace();
return null;
}
}
);
}
recover helps one recover from uncaught exceptions. In this case an HTTP 500 response doesn't result in an uncaught Throwable
Presumably, 500 means that the following block will throw an error
property = _mapper.readValue(response.getBody(), Property.class);
However, you have this wrapped in a try catch block already.

ThreadLocal and #Aspect annotation

I am using #Aspect to implement a retry logic(max_retries = 5) for database stale connection problems.In this Advice I have a ThreadLocal object which keep tracks of how many times logic has retried to get connection and it gets incremented whenever it cannot get connection so to avoid unlimited retries for stale connection issue, maximum number of retries is 5(constant).
But the problem I have is , in this #Aspect java class ThreadLocal never gets incremented and this is causing endlees loop in the code, which of course should not retry after maximun number of retries, but never reach that count and does not break out of while loop.
Please let me know if anybody had this problem with #Aspect and ThreadLcal object.
I will be happy to share the code.
private static ThreadLocal<Integer> retryCounter= new ThreadLocal<Integer>() {};
private static final String STALE_CONNECTION_EXCEPTION = "com.ibm.websphere.ce.cm.StaleConnectionException";
#Around("service")
public Object retryConnection(ProceedingJoinPoint pjp) throws Throwable {
if (staleConnectionException == null) {
return pjp.proceed();
}
Throwable exception = null;
retryCounter.set(new Integer(0));
while ( retryCounter.get() < MAX_TRIES) {
try {
return pjp.proceed();
}
catch (AppDataException he) {
exception = retry(he.getCause());
}
catch (NestedRuntimeException e) {
exception = retry(e);
}
}
if (exception != null) {
Logs.error("Stale connection exception occurred, no more retries left", this.getClass(), null);
logException(pjp, exception);
throw new AppDataException(exception);
}
return null;
}
private Throwable retry(Throwable e) throws Throwable {
if (e instanceof NestedRuntimeException && ((NestedRuntimeException)e).contains(staleConnectionException)) {
retryCounter.set(retryCounter.get()+1);
LogUtils.log("Stale connection exception occurred, retrying " + retryCounter.get() + " of " + MAX_TRIES, this.getClass());
return e;
}
else {
throw e;
}
}
As mentioned in the comments, not sure why you are using a thread local... but given that you are, what might be causing the infinite loop is recursive use of this aspect. Run it through a debugger or profile it to see if you are hitting the same aspect in a nested fashion.
To be honest, looking at your code, I think you would be better off not doing this at all, but rather just configure connection testing in your connection pool (assuming you are using one): http://pic.dhe.ibm.com/infocenter/wasinfo/v6r1/index.jsp?topic=/com.ibm.websphere.nd.multiplatform.doc/info/ae/ae/tdat_pretestconn.html

correctly printstacktrace of servlet exception

so i am using a filter to catch servlet exception (because we are using a mix of jsf/plain servlets)
when catching the ServletException and calling printstacktrace most of the information is lost.
the "true" root exception seems to be hidden behind the "funny" expression
((ServletException) e.getRootCause().getCause()).getRootCause().getCause().getCause().getCause()
this is clearly not the way to do it.
is the an easy way to print the "full" information of such an exception.
can someone explain me why the exception is wrapped this way?
Take a look at the ExceptionUtils class from commons-lang. It contains several useful methods for printing the entire chain of exceptions.
after i had a look at ExceptionUtils, this solved the problem!
final StringWriter stacktrace = new StringWriter();
ExceptionUtils.printRootCauseStackTrace(throwable,new PrintWriter(stacktrace));
msg.append(stacktrace.getBuffer());
this prints out the full stacktrace with every piece of information that is relevant.
That is called exception chaining. By wrapping an exception in a different exception you can let exceptions bubble up the stack without having your main application classes to worry about some low-level exceptions.
Example:
public void doStuff() throws StuffException {
try {
doDatabaseStuff();
} catch (DatabaseException de1) {
throw new StuffException("Could not do stuff in the database.", de1);
}
}
This way your application only has to handle StuffException but it can get to the underlying DatabaseException if it really needs to.
To get to the bottom-most (and all other) exception(s) of an exception you caught you can iterator over its root causes:
...
} catch (SomeException se1) {
Throwable t = se1;
logger.log(Level.WARNING, "Top exception", se1);
while (t.getCause() != null) {
t = t.getCause();
logger.log(Level.WARNING, "Nested exception", t);
}
// now t contains the root cause
}
Exception chaining for ServletException is tricky. Depending on the web server implementation and web development framework in use, at runtime the chain may use cause and/or rootCause. This link explains it very well. To complicate things, I've seen exceptions where the cause points to the exception itself.
Here's a recursive method we have used that covers all bases for ServletExceptions:
public static Throwable getDeepCause(Throwable ex) {
if (ex == null) {
return ex;
}
Throwable cause;
if (ex instanceof ServletException) {
cause = ((ServletException) ex).getRootCause();
if (cause == null) {
cause = ex.getCause();
}
} else {
cause = ex.getCause();
}
if (cause != null && cause != ex) {
return getDeepCause(cause);
} else {
// stop condition - reached the end of the exception chain
return ex;
}
}

Categories

Resources