feign error handling by using feign decoder - java

I have created service 'A', which needs to be calls by the service 'B' by using feign client
And if service 'A' fails due to some validation, then service 'A' send back the error response which contains the below details,
(1)http status code
(2)error message
(3) custom error map which contains the custom errorcode and their error message
for example, <"Emp-1001", "invalid employee Id">
From Service 'B' we are using feigndecoder for handling feign exception, but it only provides the http status code not the custom error code
And, In my case, for different-different scenario, the http status code is same but custom error map value is different.
on the combination of both(http status code + custom error map), we have to handle the exception in service 'B'.
kindly provide some suggestions on this

You can enable circuit breaker and also configure your application to apply different fallback methods depending on the error returned, follow the next steps:
1.- Enable the circuit breaker itself
#SpringBootApplication
#EnableFeignClients("com.perritotutorials.feign.client")
#EnableCircuitBreaker
public class FeignDemoClientApplication {
2.- Create your fallback bean
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
#Setter
private Throwable cause;
#Override
public void savePet(#RequestBody Map<String, ?> pet) {
log.error("You are on fallback interface!!! - ERROR: {}", cause);
}
}
Some things you must keep in mind for fallback implementations:
Must be marked as #Component, they are unique across the application.
Fallback bean should have a Prototype scope because we want a new one to be created for each exception.
Use constructor injection for testing purposes.
3.- Your ErrorDecoder, to implement fallback startegies depending on the HTTP error returned:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyCustomBadRequestException();
}
if (response.status() >= 500) {
return new RetryableException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
4.- In your configuration class, add the Retryer and the ErrorDecoder into the Spring context:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default();
}
You can also add customization to the Retryer:
class CustomRetryer implements Retryer {
private final int maxAttempts;
private final long backoff;
int attempt;
public CustomRetryer() {
this(2000, 5); //5 times, each 2 seconds
}
public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
#Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}
}
If you want to get a functional example about how to implement Feign in your application, read this article.

Related

Is there a good example of zalando-problem implementation in spring boot?

We are using zalando-problem for exception handling in our spring-boot application. but looks like our problem handlers are never called. Instead spring boot returns 500 Internal server error for all the exceptions. If you can provide some examples it will be helpful. I couldn't find a good example of zalando-problem implementation in spring boot
If the user is not logged in, the code is throwing SSOAuthenticationException exception.
#Immutable
public class SSOAuthenticationException extends AbstractThrowableProblem {
private final String errorMessage;
public SSOAuthenticationException( final String errorMessage ) {
super( ErrorConstants.SSO_CACHE_AUTHENTICATION_FAILED, errorMessage, Status.UNAUTHORIZED );
this.errorMessage = errorMessage;
}
public String getErrorMessage(){
return errorMessage;
}
#Override
public String toString() {
return "SSOAuthenticationException{}";
}
}
And the Exception handling code:
#ControllerAdvice
public class ExceptionTranslator implements ProblemHandling {
#Override
public ResponseEntity<Problem> process(#Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
if (entity == null) {
return entity;
}
Problem problem = entity.getBody();
if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
return entity;
}
ProblemBuilder builder = Problem.builder()
.withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
.withStatus(problem.getStatus())
.withTitle(problem.getTitle())
.with("path", request.getNativeRequest(HttpServletRequest.class).getRequestURI());
if (problem instanceof ConstraintViolationProblem) {
builder
.with("violations", ((ConstraintViolationProblem) problem).getViolations())
.with("message", ErrorConstants.ERR_VALIDATION);
} else {
builder
.withCause(((DefaultProblem) problem).getCause())
.withDetail(problem.getDetail())
.withInstance(problem.getInstance());
problem.getParameters().forEach(builder::with);
if (!problem.getParameters().containsKey("message") && problem.getStatus() != null) {
builder.with("message", "error.http." + problem.getStatus().getStatusCode());
}
}
return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
}
#ExceptionHandler(SSOAuthenticationException.class)
#ResponseBody
public ResponseEntity<Problem> handleUnAuthenticatedUser(SSOAuthenticationException ex, NativeWebRequest request) {
Problem problem = Problem.builder()
.withStatus(Status.UNAUTHORIZED)
.with("message", ErrorConstants.SSO_CACHE_AUTHENTICATION_FAILED)
.build();
return create(ex, problem, request);
}
}
When I run in debugger, I notice that the exception handler is never called. instead the code thinks there is no handler registered (In ServletInitialHandler.java, it goes to the else section which is for exception not handled) and changes the status code to INTERNAL_SERVER_ERROR. So for all the exceptions, the application throws error 500. What is wrong in the exception handling code? Do we have to include a AdviceTrait? I tried that as well. but looks like that is also not working. If you could explain the right way of handling this exception and an example, it helps. Thanks
Using Problem Spring Web 0.25.2, I first created a new AdviceTrait similar to the existing ones:
public interface CustomAdviceTrait extends AdviceTrait {
#ExceptionHandler
default ResponseEntity<Problem> handleCustomException(final CustomException exception, final NativeWebRequest request) {
return create(Status.INTERNAL_SERVER_ERROR, exception, request);
}
}
This is also where a conversion into a Problem could take place, if needed.
Then, likewise to enabling dedicated built-in advice traits I enable them by implementing the respective interface on my ExceptionHandler:
#ControllerAdvice
public class FootlooseServiceExceptionHandler implements ProblemHandling, CustomAdviceTrait { }

How can I get annotations on rpc method being called in grpc-java

I need to validate request before different rpc methods being called with different validators.
So I implemented validators like
class BarRequestValidator {
public FooServiceError validate(BarRequest request) {
if (request.bar.length > 12) {
return FooServiceError.BAR_TOO_LONG;
} else {
return null;
}
}
}
and add a custom annotation before my rpc method
class FooService extends FooServiceGrpc.FooServiceImplBase {
#Validated(validator = BarRequestValidator.class)
public void bar(BarRequest request, StreamObserver<BarResponse> responseObserver) {
// Validator should be executed before this line, and returns error once validation fails.
assert(request.bar <= 12);
}
}
But I found that I can't find a way to get annotation information in gRPC ServerInterceptor. Is there any way to implement grpc request validation like this?
You can accomplish this without having the annotation at all, and just using a plain ServerInterceptor:
Server s = ServerBuilder.forPort(...)
.addService(ServerInterceptors.intercept(myService, myValidator))
...
private final class MyValidator implements ServerInterceptor {
ServerCall.Listener interceptCall(call, headers, next) {
ServerCall.Listener listener = next.startCall(call, headers);
if (call.getMethodDescriptor().getFullMethodName().equals("service/method")) {
listener = new SimpleForwardingServerCallListener(listener) {
#Override
void onMessage(request) {
validate(request);
}
}
}
return listener;
}
}
Note that I'm skipping most of the boilerplate here. When a request comes in, the interceptor gets it first and checks to see if its for the method it was expecting. If so, it does extra validation. In the generated code you can reference the existing MethodDescriptors rather than copying the name out like above.

Nested exception is not correctly reported in the ControllerAdvice

In a rest application, i create a class to manage error
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ProcessPaymentException.class)
private ResponseEntity < String > handleProcessPaymentException(ProcessPaymentException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
#ExceptionHandler(Exception.class)
private ResponseEntity < String > defaultExceptionHandler(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
In my service layer
#Transactional
#Override
public void processPayment(Long paymentId, PaymentModeEnum paymentMode) throws ProcessPaymentException {
processCreditCardPayment(paymentId, paymentMode);
}
private void processCreditCardPayment(Long paymentId, PaymentModeEnum paymentMode) throws ProcessPaymentException {
try{
chargePayment(paymentId)
}catch(ProcessPaymentException ppe){
throw new ProcessPaymentException(ppe.getMessage()); #1
}
}
private ResolverReceipt chargeMemberCreditCard(Long paymentId, PaymentGatewayConfig paymentGateway) throws ProcessPaymentException {
...
if(memberId==null){
throw new ProcessPaymentException("error process payment");#2
}
}
When i get a ProcessPaymentException, i see in debug mode, when i go in the RestResponseEntityExceptionHandler, i pass by defaultExceptionHandler.
I don't understand why, i was thinking to pass handleProcessPaymentException method.
The debug message i see is:
Target object must not be null; nested exception is java.lang.IllegalArgumentException: Target object must not be null.
e= (org.springframework.dao.InvalidDataAccessApiUsageException) org.springframework.dao.InvalidDataAccessApiUsageException: Target object must not be null; nested exception is java.lang.IllegalArgumentException: Target object must not be null
I was thinking to get: error process payment
According to this article, if you have used custom resolver, you need to define following lines to use #ExceptionHandler.
#Component
public class AnnotatedExceptionResolver extends AnnotationMethodHandlerExceptionResolver
{
public AnnotatedExceptionResolver() {
setOrder(HIGHEST_PRECEDENCE);
}
}

Order of #ExceptionHandler

I use #ControllerAdvice to handle all my app exceptions :
#ControllerAdvice
public class ExceptionHandlingController {
#ExceptionHandler({UnauthorizedException.class})
public String unauthorizedException() {
.........
}
#ExceptionHandler({UnauthorizedAjaxException.class})
#ResponseBody
public void unauthorizedAjaxException() {
.........
}
#ExceptionHandler({Exception.class})
public String globalException(){
.........
}
}
And somewhere in my code i do throw new UnauthorizedException();
#Around("#annotation(Authenticated)")
public Object profilingAuthentication(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
if( request.getSession().getAttribute("idContact") == null ) {
if( "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) )
throw new UnauthorizedAjaxException();
throw new UnauthorizedException();
}
return pjp.proceed();
}
But sadly Spring MVC appears to be acting random by using the most generic case (Exception) rather than more specific ones (UnauthorizedException for example). And sometimes he choose the correct one !
How the order works works ? and is there any way to specify the order ?
UnauthorizedException is a custom exception
public class UnauthorizedException extends Exception {
public UnauthorizedException(){
super();
}
public UnauthorizedException(String message){
super(message);
}
}
UPDATE
i found out that the order it's not rondom actually the methods who throw UnauthorizedException works normally but the others not !
#Authenticated
#RequestMapping(value="/favoris")
public String favoris(ModelMap model, HttpServletRequest request)
throws UnauthorizedException {
....
}
#Authenticated
#RequestMapping(value="/follow")
public String follow(ModelMap model, HttpServletRequest request) {
.....
}
So i have to add throws UnauthorizedException manually or there is some other solution ?
we are using exception handler in following way and never order get mixed and it work as expected.
So it could be possible if you will use it as following example then it will solve your problems
**********handler class******************
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = Exception.class)
public boolean handle1(Exception exc) {
System.out.println("#####Global Exception###" + exc);
exc.printStackTrace(System.out);
return true;
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = CustomException.class)
public boolean handle2(CustomException exc) {
System.out.println("###custom exception######" + exc);
exc.printStackTrace(System.out);
return true;
}
}
***************Controller class************
#RestController("test")
#RequestMapping("/test1")
public class TestController {
#RequestMapping("/t1")
public boolean test() {
if (true) {
throw new CustomException();
}
return true;
}
}
In above example exception habdler is handle2 because 1st of all it will search for matching exception if not found then go for parrent handler
If we throw new NullPointerException() then it will search for matching handler but not found in this case then go for parrent that is handle1
for more you can refer here
I hope it will help you. Thanks
Use an annotation #Order or implement an interface Ordered for #ControllerAdvice.
See implementations of:
ExceptionHandlerMethodResolver class
ExceptionHandlerExceptionResolver class (method initExceptionHandlerAdviceCache)
AnnotationAwareOrderComparator class
There is no order/priority as long as you have a single controlleradvice class in your project. But if you have multiple controlleradvice classes, you can set the Order. But here, in your case, the order is not applicable as the two exceptions are handled differently (i.e., UnauthorizedException and Exception).
The good thing is, Spring will automatically find the respective custom Exception class (if any,
otherwise generic Exception) and invoke the corresponding method.
Please refer for more information on Spring Controller Advice and Exception handling:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

Java Jersey PathParams Checking and NotFoundException custom message

I am using Jersey for rest API, JerseyTests to unit test.
I have been following what seems to be conventional practice over the internet for PathParams checking and Exception Handling, but I don't quite understand what I am doing wrong here:
RoomApplicationResource.java
#Path("demandes")
public class RoomApplicationResource {
#GET
#Path("/{email}/{requestNumber}")
public Response getRoomApplication(
#PathParam("email") String email,
#PathParam("requestNumber") String requestNumber) throws NoRoomApplicationFoundException {
if (email == "wrong#email.com" || requestNumber == "wrong") {
throw new NoRoomApplicationFoundException("bad request");
}
String response =requestNumber+" is valid for "+email;
return Response.ok(response).build();
}
}
I handle Exceptions like this:
NotFoundMapper.java
#Provider
public class NotFoundMapper implements ExceptionMapper<NoRoomApplicationFoundException>{
#Override
public Response toResponse(NoRoomApplicationFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.entity(e.getMessage()).build();
}
}
NoRoomApplicationFoundException.java
public class NoRoomApplicationFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NoRoomApplicationFoundException() {
super();
}
public NoRoomApplicationFoundException(String msg) {
super(msg);
}
public NoRoomApplicationFoundException(String msg, Exception e) {
super(msg, e);
}
}
And I test like this:
RoomApplicationResourceTest.java
public class RoomApplicationResourceTest extends JerseyTest {
#Override
protected Application configure() {
return new ResourceConfig(RoomApplicationResource.class, NotFoundMapper.class);
}
// This test works fine as expected
#Test
public void whenParametersAreExistantReturnTheOkResponse() {
final Response res = target("demandes").path("valid#email.com").path("12345").request().get();
assertEquals(200, res.getStatus());
assertEquals("12345 is valid for valid#email.com", res.readEntity(String.class));
}
// This does not work as expected
#Test
public void whenEmailParameterDoNotMatchToAnyRoomApplicationThenReturns404() {
final Response res = target("demandes").path("wrong#email.com").path("12345").request().get();
assertEquals(404, res.getStatus());
assertEquals("bad request", res.readEntity(String.class));
}
}
Question 1: Is this way of doing conditional checking on params wrong? The result of the second test where the email is invalid should throw my custom exception and return a 404, but instead returns a 200 and the valid message.
Question 2: How should I handle missing parameters in this case? It seems Jersey throws a NotFoundException by default. Is there a simple way to customize the message of that error or perhaps use my custom exception as the throws NoRoomApplicationFoundException at the end of my resource method does not seem to be doing anything?
Thanks in Advance. Alex
Question 1
Yes. The problem is your use of == to compare Strings. You should instead be using String.equals(). See How do I compare Strings in Java?
if ("wrong#email.com".equals(email) || "wrong".equals(requestNumber)) {
throw new NoRoomApplicationFoundException("bad request");
}
Question 2:
This question seems to be related to your first question. But for me, as a general rule (this is just me), if I am authoring the exception class and the exception is specific to my JAX-RS application (meaning I will have no use for it outside the JAX-RS application), I will just make the exception extend WebApplicationException. This exception will be handled by default, and you can create the Response in that class. No need for any ExceptionMapper. For example
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
public class NoRoomApplicationFoundException extends WebApplicationException {
private static final long serialVersionUID = 1L;
public NoRoomApplicationFoundException() {
this("Room not found", 400);
}
public NoRoomApplicationFoundException(String msg, int status) {
this(Response.status(status).entity(msg).build());
}
public NoRoomApplicationFoundException(Response response) {
super(response);
}
}
You could completely get rid of the NotFoundMapper and this would work just fine.
if ("wrong#email.com".equals(email) || "wrong".equals(requestNumber)) {
throw new NoRoomApplicationFoundException();
}
Some Resources:
Jersey Documentation for Exception Handling
WebApplicationException Javadoc to see different constructors. There are alot more ways you can construct your exception and chain up to the super class
What should I return if my object is null?. In general, if a resource can't be found, the general rule of thumb is to return a 404 Not Found. Not sure it applies to this specific case though.

Categories

Resources