ControllerAdvice annotation working incorrectly - java

So i'm trying to cover my project on exception side and used #ControllerAdvice like so
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception exception) {
return ResponseEntity.badRequest().body(new ErrorResponse());
}
}
and everything was good until i got StackOverflowError, because i used #Entity with onToMany fields. In this case i got response with confidential information from other entities AND ErrorResponse at the end. So i see that at some point ControllerAdvice did his job but still gave incorrect response. I got rid of this problem, but I'm very worried that this will happen again at some point. What can i do so ControllerAdvice would catch StackOverflowError too and give response that i specialized for this.

It really isn't advised to handle an java.lang.Error but you only have to add another handler:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(Exception exception) {
return ResponseEntity.badRequest().body(new ErrorResponse());
}
#ExceptionHandler(StackOverflowError.class)
public ResponseEntity<?> handleStackOverflow(StackOverflowError error) {
return ResponseEntity.badRequest().body(new ErrorResponse());
}
}

Related

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);
}
}

How do I get the Exception class that was thrown in my spring error controller

I have my Spring error controller, and I need to get the actual exception class so I can print stack trace and other things like that.
This is my error controller
#Controller
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
#RequestMapping("/error")
public String handleError() {
return "somethingwentwrong";
}
#Override
public String getErrorPath() {
return null;
}
}
I know its not much, but I need the exception object to be able to do some extra handling.
If you wish to execute different code based on the TYPE of the exception thrown, you should look at using #ControllerAdvice along with #ExceptionHandler. Any exceptions that you do not handle with an #ExceptionHandler will then bubble up to the default ErrorController (though you could handle Exception in a handler and then all exceptions will be handled via your custom handler). Something like:
#ControllerAdvice
#RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
protected ResponseEntity<String> handleMyException(MyException ex) {
// Your code here
}
#ExceptionHandler(Exception.class)
protected ResponseEntity<String> handleException(Exception ex){
// Your code here
}
}
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler
No worries guys, I solved the problem, I just had to do some digging into springs built in error controller to get the stacktrace.

#Valid is not working with jax rs and springboot

I am getting NotFoundException while trying to implement custom exception handling in spring-boot rest application.
The code was working fine when I was using MVC (using #ControllerAdvice) annotations but not sure when I am sending a data which is violating the constraint mentioned in entity(pojo class) it is throwing only NotFoundException (for all validation failure) but not the MethodViolationException or ConstraintViolationException
I am not able to send the message for that particular violation.
Not sure where I am making this mistake. Please help
Code:
#POST
#Path("/customers/add")
public Response addCustomer(#Valid customer cust)
{
// Rest of the code
}
POJO:
#Entity
#Table(name="cust")
public class Customer
{
#NotNull
#Size(min=1,max=50,message ="invalid name")
String name;
}
Exception Handler:
#Provider
public class CustomHandler implements ExceptionMapper<Exception>
{
public Response toResponse(Exception ex)
{
if(ex instanceOf ConstraintViolationException)
{
Do something
}
}
**UPDATE 1
If I enable the send_error_in_response i am getting the message for this but not sure why my custom exception handler is not able to catch this exception and only throwing NotFoundException
Try Handling Exception Using:
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(StudentNotFoundException)
public final ResponseEntity<ErrorDetails> handleUserNotFoundException(StudentNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
For more information you might want to refer http://www.springboottutorial.com/spring-boot-validation-for-rest-services

Spring Boot returns Whitelabel Error Page instead of JSON

I need RESTful service to return JSON every time regardless if there is error or not. With normal cases everything goes ok, but when exceptions come I see Whitelabel Error Page.
I tried to resolve this in two ways.
Through #ExceptionHandler inside #Controller class inside the class annotated with #RestController:
#ExceptionHandler(MyCustomException.class)
#ResponseBody
public ErrorResponse handleException(Exception e) {
return new ErrorResponse(5, "Error message");
}
And through special class with #ControllerAdvice annotation:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(MyCustomException.class)
#ResponseBody
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleSecurityException(MyCustomException e) {
return new ErrorResponse(5, "Error message");
}
}
In both cases through breakpoints I see that those methods was called, but I still receive Whitelabel Error Page.
So what am I missing or doing wrong?
The problem was I forgot to implement getters and setters for ErrorResponse class.

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