ExceptionHandler not working with Spring Cloud and Aws Lambda - java

I created a function on AWS Lambda with Spring Cloud and java 11.
I'm trying to catch the exceptions thrown with #ExceptionHandler but it's not working.
I'm using an API Gateway Http Api as a trigger
Is there any specific way to catch these exceptions with Spring Cloud?
Function
#Component
public class TestFunction implements Function<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse> {
#Override
public APIGatewayV2HTTPResponse apply(APIGatewayV2HTTPEvent event) {
throw new TestException();
}
}
Handle
#ControllerAdvice
public class TestExceptionHandler {
#ExceptionHandler(value = {TestException.class})
public APIGatewayV2HTTPResponse handleTestException(TestException e) {
return APIGatewayV2HTTPResponse.builder()
.withStatusCode(422).build();
}
}
Handle - second attempt as suggested by #kladderradatsch
#ControllerAdvice(basePackageClasses = TestFunction.class)
public class TestExceptionHandler extends ResponseEntityExceptionHandler {
#ResponseBody
#ExceptionHandler(value = {TestException.class})
public ResponseEntity<?> handleTestException(TestException ex) {
return new ResponseEntity<>(new MyErrorBody(400, ex.getMessage()), HttpStatus.BAD_REQUEST);
}
}
Exception
public class TestException extends RuntimeException {
public TestException() {
super("Error Test.");
}
}
I tried to catch the exception in several different ways, including adding a #ResponseStatus(HttpStatus.BAD_REQUEST) to the exception, but that didn't work either.

Related

Issue with ExceptionMapper in Jersey

I have two classes that implements ExceptionMapper interface.
IllegalArgumentExceptionMapper to handle IllegalArgumentException:
#Slf4j
#Provider
public class IllegalArgumentExceptionMapper implements ExceptionMapper<IllegalArgumentException> {
#Override
public Response toResponse(IllegalArgumentException exception) {
log.info("IllegalArgumentExceptionMapper!");
Error error =
Error.builder()
.statusCode(HttpStatus.SC_BAD_REQUEST)
.statusDescription(exception.getLocalizedMessage())
.errorMessage(exception.getMessage())
.build();
return Response.status(Response.Status.BAD_REQUEST)
.entity(error)
.type(MediaType.APPLICATION_JSON)
.build();
}
}
GenericExceptionMapper is an ExceptionMapper that I want to use as the default ExceptionMapper when an exception is not mapped to any of my other specific ExceptionMapper classes. Here it is:
#Slf4j
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable ex) {
log.info("GenericExceptionMapper!");
Response.StatusType type = getStatusType(ex);
Error error = Error.builder()
.statusCode(type.getStatusCode())
.statusDescription(type.getReasonPhrase())
.errorMessage(ex.getLocalizedMessage())
.build();
return Response.status(error.getStatusCode())
.entity(error)
.type(MediaType.APPLICATION_JSON)
.build();
}
private Response.StatusType getStatusType(Throwable ex) {
if (ex instanceof WebApplicationException) {
return((WebApplicationException)ex).getResponse().getStatusInfo();
} else {
return Response.Status.INTERNAL_SERVER_ERROR;
}
}
}
However, when I try to throw an IllegalArgumentException, with:
throw new IllegalArgumentException("Just a normal IllegalArgumentException!");
I see that GenericExceptionMapper instead of IllegalArgumentExceptionMapper is being used.(I see "GenericExceptionMapper!" in the log).
Any idea what went wrong?
Some observations
If I delete GenericExceptionMapper, IllegalArgumentExceptionMapper is still not being called. So I think there is an issue for my IllegalArgumentExceptionMapper implementation.
If I modify IllegalArgumentExceptionMapper with public class IllegalArgumentExceptionMapper implements ExceptionMapper<RuntimeException> and throw new RuntimeException, then I see that IllegalArgumentExceptionMapper is being used.

Correct Pattern for handling Service Layer results

So I've been using Spring and Java for a while to build microservices. I am concerned by the way I am currently handling service layer results which uses "business exception"
Controller
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<?> checkout(#RequestBody CheckoutRequest body) {
try {
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
catch (UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
catch (CustomBusinessException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
}
}
Service
#Service
public class PurchaseService {
// ...
public DTO doCheckout(CheckoutRequest request) {
// this one calls another microservice
if (!isUserValid(request.userId)) {
// current handling of business rules violation (1)
throw new UnauthorizedException("User not valid");
}
if (request.total < 10) {
// current handling of business rules violation (2)
throw new CustomBusinessException("Minimum checkout at 20 dollars");
}
// ... do actual checkout
return new DTO(someDTOData);
}
}
I was comfortable at using this "pattern" because I do not need to "if" the business result in the controller level to return the appropriate HttpStatusCode, but since I've found some articles saying that exception is expensive specifically in Java, I doubt what I was doing is good for the long run.
Is there another correct way to gracefully handles the business result layer?
The problem with ResponseEntity in Spring is that they are typed with the result object you want to return when the endpoint is called successfully, so you can't return another body different from the happy path one, that in your case would be SameDTO. One way to address this issue is to use ? as the type of the response entity, as you have done but it is not the most recommended way.
So the best way to do this is precisely to use exceptions when there is a situation when you can't return the expected object and you have to return another object or status code, but instead of using a try-catch in the controller you should use an exception handler (Controller Advice) https://www.baeldung.com/exception-handling-for-rest-with-spring.
This controller advice would catch any exception thrown in your application and depending on the exception type it could return a different response class or status code without affecting the main controller. One example of how can be your controller advice would be:
#ControllerAdvice
public class ErrorHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleInternal(final RuntimeException ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ex.getMessage());
}
#ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ResponseDto> identityClientException(UnauthorizedException e) {
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(e.getMessage());
}
#ExceptionHandler(CustomBusinessException.class)
public ResponseEntity<ResponseDto> identityClientException(CustomBusinessException e) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
And your controller woulb be much more clean without exception handling logic:
#RestController
public class PurchaseController {
#Autowired
private PurchaseService purchaseService;
#PostMapping("/checkout")
public ResponseEntity<SomeDTO> checkout(#RequestBody CheckoutRequest body){
SomeDTO dto = purchaseService.doCheckout(body);
return ResponseEntity.ok(dto);
}
}

Ambiguous #ExceptionHandler method for HttpMessageNotReadableException

In my #RestController I'm successfully handling JSONParse exceptions coming from #RequestBody (for example, a String wrongly entered into an Integer field). This is the code:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({ HttpMessageNotReadableException.class })
public ValidationError handleException(HttpMessageNotReadableException ex) {
if (ex.getCause() instanceof InvalidFormatException) {
...
} else {
throw ex;
}
}
Now I want to move this to a #ControllerAdvice to be used by many controllers. Here it is:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({ HttpMessageNotReadableException.class })
public ValidationError handleException(HttpMessageNotReadableException ex) {
if (ex.getCause() instanceof InvalidFormatException) {
...
} else {
throw ex;
}
}
But Spring complains with the following:
Ambiguous #ExceptionHandler method mapped for [class org.springframework.http.converter.HttpMessageNotReadableException]: {public Object foo.bar.RestExceptionHandler.handleException(org.springframework.http.converter.HttpMessageNotReadableException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception}
I can't override ResponseEntityExceptionHandler.handleException because it's final. What other options are there?
Using Spring Boot 2.4.3.
I can't override ResponseEntityExceptionHandler.handleException because it's final
You're supposed to override the protected ResponseEntity<Object> handleHttpMessageNotReadable(...) method instead for custom error handling.
You should not extend from ResponseEntityExceptionHandler class.
Check this answer https://stackoverflow.com/a/71119336/4757784

Hystrix - how to register ExceptionMapper

My Hystrix/Feign app makes calls to other web services.
I would like to propagate error codes/messages from these web services.
I implemented ErrorDecoder, which correctly decodes exceptions returned and rethrow them.
Unfortunately these Exceptions are wrapped by HystrixRuntimeException and the JSON returned in not what I want (generic error message, always 500 http status).
Most likely I need an ExceptionMapper, I created one like this:
#Provider
public class GlobalExceptionHandler implements
ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable e) {
System.out.println("ABCD 1");
if(e instanceof HystrixRuntimeException){
System.out.println("ABCD 2");
if(e.getCause() != null && e.getCause() instanceof HttpStatusCodeException)
{
System.out.println("ABCD 3");
HttpStatusCodeException exc = (HttpStatusCodeException)e.getCause();
return Response.status(exc.getStatusCode().value())
.entity(exc.getMessage())
.type(MediaType.APPLICATION_JSON).build();
}
}
return Response.status(500).entity("Internal server error").build();
}
}
Unfortunately this code is not being picked-up by my application (debug statements are not visible in logs).
How could I register it with my Application?
I couldn't make use of an ExceptionMapper.
I solved this problem using ResponseEntityExceptionHandler.
Here is the code:
#EnableWebMvc
#ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(HystrixRuntimeException.class)
#ResponseBody
ResponseEntity<String> handleControllerException(HttpServletRequest req, Throwable ex) {
if(ex instanceof HystrixRuntimeException) {
HttpStatusCodeException exc = (HttpStatusCodeException)ex.getCause();
return new ResponseEntity<>(exc.getResponseBodyAsString(), exc.getStatusCode());
}
return new ResponseEntity<String>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}

How to have two ControllerAdvice in the same SpringMvc application

I would like to manage Exception thrown by simple Controller or RestController in two ways:
1) html redirection
2) Json error
I tested the code below :
#ControllerAdvice(annotations = Controller.class)
public class ExceptionHandlerController
{
#ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest _req, Exception _ex)
{
K_LOGGER.info("test");
return new ModelAndView();
}
}
#ControllerAdvice(annotations = RestController.class)
public class ExceptionHandlerRestController
{
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest _req, Exception _ex)
{
return new ResponseEntity<>("test", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#RestController
public class GreetingController
{
#RequestMapping("/greetingexception")
public Greeting greetingException(#RequestParam(value = "name", defaultValue = "World") String name)
throws Exception
{
throw new Exception();
}
}
It doesn't work properly, I always pass by ExceptionHandlerController but not by ExceptionHandlerRestController.
I think it's because #RestController inherit of #Controller.
Do you have a other solution?
Try to add #Order(Ordered.HIGHEST_PRECEDENCE) annotation to rest exception handler. It may helps you.
eg04lt3r answer is correct, just though that more details might be useful for someone.
In case when you have global #ControllerAdvice and want to handle some exception in a different way in one of your Controllers you need to set #Order(Ordered.HIGHEST_PRECEDENCE) on the #ControllerAdvice which should have higher priority.
For example:
#ControllerAdvice
public class GeneralExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#ControllerAdvice(assignableTypes = MyController.class)
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#Order is needed because on startup one of the handlers will register with higher order automatically, anyway and your exception handling will become unpredictable. For example I recently saw a case when if you start an app using bootRun gradle task MyExceptionHandler was primary, but when started as jar GeneralExceptionHandler was primary.

Categories