The below is the "ControllerAdvice" and it acts as the Global Exception Handler for Multiple MicroServices.
// Custom Exception Handler
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<ErrorResponse> customException(CustomException ex) {
ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
return new ResponseEntity<>(error, ex.getStatus());
}
@ExceptionHandler(value = Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse globalException(Exception ex) {
return new ErrorMessage("INTERNAL_SERVER_ERROR", "500");
}
Now for One of my MicroService, I need to customize the Error Response coming from the above Exception Handler i.e I need to Wrap the "ErrorResponse" coming from the Global Exception Handler under another Object called "ErrorResponseWrapper"
class ErrorResponseWrapper extends ErrorResponse {
String type;
// getter & setters
}
Now in My Microservice where I need to Customise the error Response, I am just Overriding the GlobalExceptionHandler methods
@RestControllerAdvice
public class ControllerException extends GlobalExceptionHandler {
// This Works FINE...
@Override
public ErrorResponseWrapper globalException(Exception ex) {
//
ErrorResponse error = super.globalException(ex);
// New Response Wrapper
ErrorResponseWrapper wrapper = new ResponseWrapper();
wrapper.setType("SOMETHING");
return wrapper;
}
//Issue is here:-
// I get Compilation Error: ReturnType is Not Compatible with the GlobalException return type.
@Override
public ResponseEntity<ErrorResponseWrapper> customException(CustomException ex) {
ErrorResponse error = super.customException(ex).getBody();
// New Response Wrapper
ErrorResponseWrapper wrapper = new ResponseWrapper();
wrapper.setType("SOMETHING");
return new ResponseEntity<>(wrapper,ex.getStatus());
}
}
It works fine with the First Overridden method, why is it different from the ResponseEntity? Is there anything I am doing wrong, or how do I achieve this?
CodePudding user response:
Your problem is generics, and that they are invariant by default, which trips up loads of people and may not immediately come across as logical or correct (but, it is - keep reading).
There are these 3 concepts, called invariant / covariant / contravariant.
Covariant
covariant means that a subtype of a thing is just as good as the thing.
Java basic typing is covariant. Here:
Integer x = 5;
Number n = x; // This compiles and runs just great.
Note how the type of x
is Integer, which is a subtype of Number
, and yet that is fine here. That's because covariance is applied here.
Contravariance
Contravariance is the inverse: A supertype is a fair standin. This is actually part of java: When overriding methods, you can use supertypes for parameters and that is fine. This compiles:
public class Parent {
public void foo(String x) {}
}
public class Child extends Parent {
@Override public void foo(Object x) {}
}
The reason this is okay should be obvious: Because common sense: All strings are also objects therefore the foo
method that Child defines can handle all possible cases of invoking foo
whenever an instance of Child
is treated as if its a Parent
. It can handle all of these (that'd be when x
is a String
), and more to boot.
Generics and variance
Generics is invariant by default. That's a head twister! Let's check it out:
List<String> list1 = new ArrayList<String>();
List<Object> list2 = list1; // DOES NOT COMPILE!!
That's bizarre, isn't it? But it makes sense! After all, if the above code works, list1
and list2
are both pointing at the same actual list; therefore if you make a change to this one list via the list1
ref, you'd be able to see the change when using the list2
ref. But, therein lies the problem: I can run list2.add(new Object())
, but that would mean that list1.get()
returns an Object
and not a String
.
That is why generics are invariant: Because the universe works that way.
This applies everywhere. Thus, we get to the crux of your problem here:
ResponseEntity<ErrorResponseWrapper>
is not a subtype of ResponseEntity<ErrorResponse>
!
Fortunately, generics are quite flexible. You can have covariance and even contravariance if you want it; just, ask for it. Here, now it works fine:
public class Parent {
public ResponseEntity<? extends ErrorResponse> foo() {}
}
public class Child extends Parent {
@Override public ResponseEntity<? extends ErrorResponseWrapper> foo() {}
}
That does work fine. ? extends
is java-ese for: I want generics, but covariant. The compiler will then help you out and prevent you from breaking the world. Let's try it:
List<String> list1 = new ArrayList<>();
List<? extends Object> list2 = list1; // now it compiles!
list2.add(new Object()); // but this does not!
Calling .add()
on any List<? extends Y>
where Y can be anything you please does not compile, unless you pass a literal null
and only because null
is every type. That is on purpose - it lets you assign that list-of-strings to the ref.
The fix
Go back to the original type and update the return type. If you can't do that, there is nothing you can do here.
CodePudding user response:
While defining the CustomHandler you need to use
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<? extends ErrorResponse> customException(CustomException ex) {
ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
return new ResponseEntity<>(error, ex.getStatus());
}
CodePudding user response:
Try the following in your original customException
method:
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<T extends ErrorResponse> customException(CustomException ex) {
ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
return new ResponseEntity<>(error, ex.getStatus());
}