#ControllerAdvice even by setting the highest precedense for RestControllers not working as it should - spring-boot

I am using SpringBoot 5.
I want to catch all exception thrown from RestController and display customize message format.
I have simplified the situation like below:
The RestController
#RestController
#RequestMapping("/test")
public class TestRestController {
#Autowired
private TestService testService;
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable int id) {
Test test = testService.find(id);
if(department!=null){
throw CustomException();
}
return new ResponseEntity<>(test, HttpStatus.OK);
}
}
The ControllerAdvice Exception handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice(annotations = RestController.class)
public class RestExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);
#ExceptionHandler(value= {CustomException.class})
public ResponseEntity<ErrorDetail> handleCustomException(CustomException exception,
HttpServletRequest request) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setStatus(HttpStatus.NOT_FOUND.value());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return new ResponseEntity<>(errorDetail, null, HttpStatus.NOT_FOUND);
}
}
The problem it is that RestExceptionHandler is not working, it is not catching the exception and returning the modified error message format. It seem my RestExceptionControllerClass is not overriding the GlobalExceptionHandler. I don't know why this is happening because I have marked the RestExceptionHandler with the highest precedense. I will appriciate any guidence to debug this problem.

#ControllerAdvice
public class RestExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
#ExceptionHandler(CustomException.class)
public ErrorDetail handleCustomException(CustomException exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return errorDetail;
}
}
Refer this link to know more about exception handling for REST API
https://www.baeldung.com/exception-handling-for-rest-with-spring

Change your RestExceptionHandler class like below
#RestControllerAdvice
public class RestExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorDetail> handleCustomException(CustomException exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setTimeStamp(Instant.now().getEpochSecond());
errorDetail.setTitle("Resource Not Found");
errorDetail.setDetail(exception.getMessage());
errorDetail.setDeveloperMessage(exception.getClass().getName());
return new ResponseEntity<>(errorDetail, null, HttpStatus.NOT_FOUND);
}
}
And you also need to extends RuntimeException in your CustomException class

The problem was that another exception was thrown before my CustomException. In the service call , there was part of code that threw an exception that i did not expect. So my RestExceptionHandler couldn't catch the exception because it didn't have a method to handle that exception and so the GlobalExceptionHandler was handling that exception. After fixing the code and made sure that the CustomExeption was thrown everything worked as it should. The RestExceptionHandler handled exception and printed the custom message.

Related

I want to return status code of 400 instead of 500 about unhandled exceptions, globally

I am trying to make 500 Internal Server Error to 400 Bad Request in every cases.
This is my package structure for exceptions.
For example, ConflictExeption looks like this.
#ResponseStatus(HttpStatus.CONFLICT)
public class ConflictException extends ApiException {
public ConflictException(String message) {
super(message);
}
public ConflictException() {
this("Conflict Exception.");
}
}
public class ApiException extends RuntimeException {
public ApiException(String message) {
super(message);
}
}
public class UserEmailInUseException extends ConflictException{
public static final String MESSAGE = "Desired email is already in use.";
public UserEmailInUseException() {
super(MESSAGE);
}
}
Below is my simple service code.
#Service
public class UsersService {
#Transactional
public UserInfoResponseDto signUp(UserSignupRequestDto requestDto) {
if (usersRepository.findByEmail(requestDto.getEmail()).isPresent()) throw new
UserEmailInUseException();
return new UserInfoResponseDto(usersRepository.save(requestDto.toEntity()));
}
}
UserEmailInUseException extends ConflictException.
In my case, 500 error occurs when some violations are made while making transaction with MariaDB.
Simply put, I just want to return status code of 400 instead of 500, where exception is not handled.
Thanks in advance!
Also, I've tried this example below, and it seems to send 500..
Link
Have you tried:
#RestControllerAdvice
public class RestErrorHandler {
#ExceptionHandler
#ResponseStatus(BAD_REQUEST)
Exception handleUnhandledException(Exception e) {
return e;
}
}
First of all you are mapping it to CONFLICT instead of BAD_REQUEST. Have you tried the following annotation setting?
#ResponseStatus(HttpStatus.BAD_REQUEST)
If you don't want to change existing ConflictException try to introduce a new base exception as follows:
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends ApiException {
// [...]
}

Spring-boot AOP advice with CompletableFuture

I try to log with AOP for a CompletableFuture controller. #Before advice is working OK. But with #AfterReturning it is not working correctly in the exception case. I also tried with #AfterThrowing, which is not working either. When an exception occurs, my #AfterReturning advice also is not triggered and #AfterThrowing is never reached.
How can I use an AOP advice with exceptions in this case?
JController:
#RestController
public class JController extends BaseExceptionHandler {
#GetMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public CompletableFuture<BaseResponse> search() {
final CompletableFuture result = asyncProcessor.process(request);
return result;
}
}
BaseExceptionHandler:
public class BaseExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity handleException(final Exception exception) {
return new ResponseEntity<>(new ErrorResponse(Message.INTERNAL_SERVER_ERROR, StatusCode.UNKNOWN_ERROR), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
AOP Class
#AfterReturning(value = "execution(* com.xxx.xxx.controller.*.*(..))", returning = "result")
public void outgoingSuccess(final JoinPoint joinPoint, final CompletableFuture result) {
LOGGER.debug("After Returning method: " + joinPoint.getTarget().getClass().getSimpleName());
}
#AfterThrowing("execution(* com.xxx.xxx.controller.*.*(..))")
public void outgoingError(final JoinPoint joinPoint) {
LOGGER.debug("After Throwing method: " + joinPoint.getTarget().getClass().getSimpleName());
}

Is there a way in spring boot to manually invoke the Exception Advice?

I have a scenario where is an already existing controller and the service throws exceptions which are handled via the #RestControllerAdvice.
Now i have a new class which i have introduced which invokes methods from the above service class in a batch mode. In my class i have to capture the exceptions or successes bundle them up and return. For any exceptions that occur i need to report the HTTP Status and the error message.
Could you let me know if there is any way this can be achieved?
You can create your own Exception class.
public class MyException extends Exception {
private int errorCode;
private String errorMessage;
public MyException(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
and you can create new MyException when occurring any exception and throw it. Then you get this exception in the #RestControllerAdvice class.
#RestControllerAdvice
public class ExceptionAdvice {
private ErrorCodeMapper errorCodeMapper;
#Autowired
public ExceptionAdvice(ErrorCodeMapper errorCodeMapper) {
this.errorCodeMapper = errorCodeMapper;
}
#ExceptionHandler(value = MyException.class)
public ResponseEntity handleGenericNotFoundException(MyException e) {
return new ResponseEntity(errorCodeMapper.getStatusCode(e.getErrorCode()));
}
}
and mapper class like below:
#Service
public class ErrorCodeMapper {
public static Map<Integer,HttpStatus> errorCodeMap = new HashMap<>();
public ErrorCodeMapper(){
errorCodeMap.put(100, HttpStatus.BAD_REQUEST);
errorCodeMap.put(101,HttpStatus.OK);
errorCodeMap.put(102,HttpStatus.BAD_REQUEST);
errorCodeMap.put(103,HttpStatus.BAD_REQUEST);
}
HttpStatus getStatusCode(int errorCode){
return errorCodeMap.get(errorCode);
}
}
You can more details to MyException and add the error message to the ResponseEntity.

Get exception object in custom error controller

I am using spring boot and write a global exception handler use AbstractErrorController. How could i get an exception object in controller?
#Controller
public class MyCustomErrorController extends AbstractErrorController {
public MyCustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#RequestMapping("/error")
public void handleError(HttpServletRequest req, HttpServletResponse resp) {
Exception e = ...; // how to get exception here
log.error(e);
displayError(req, resp, e);
}
#Override
public String getErrorPath() {
return "/error";
}
}
You can get the exception from the HttpServletRequest as follows:
#Controller
public class MyCustomErrorController extends AbstractErrorController {
#RequestMapping("/error")
public void handleError(HttpServletRequest request) {
Exception e = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
...
}
}
An handler intercepts an Exception generated or re-thrown by a controller. It doesn't have an endpoint because it usually does it for all the controllers in your application. The Handler instructs the application server to return a specific error when a specific Exception is thrown.
Here is an example:
#ControllerAdvice // Specialization of #Component for classes that declare #ExceptionHandler, #InitBinder, or #ModelAttribute methods to be shared across multiple #Controller classes.
public class ResourceNotFoundExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getLocalizedMessage(), ex);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
ResponseEntity<Object> response = new ResponseEntity<>(error, headers, HttpStatus.NOT_FOUND);
return response;
}
}
In this example ApiError is a data structure that reports the error to the UI. What this code does is intercepting the Exception "ResourceNotFoundException", create an appropriate error Data transfer object, set the response HttpStatus and headers and return the error.
you can find a different example here: https://github.com/otrebor/springbootseed-openshift/blob/master/src/main/java/com/company/example/springbootseed/core/errorhandling/handlers/
Add Exception as an extra parameter to handleError()

Spring Boot 2.1 : Exception thrown in WebMvcConfigurer#addFormatters(...) not catched in #RestControllerAdvice

After updating from Spring Boot 2.0 to 2.1, all exceptions thrown in WebMvcConfigurer#addFormatters( FormatterRegistry registry ) are no longer catched in the #RestControllerAdvice. I use this method for additionnal converters.
Eg.:
public class ConvertersContainer {
public static class StringToStatusConverter implements Converter<String, Status> {
#Override
public Status convert( String source ) {
return Status.findStatus( source );
}
}
}
And Status is an enum.
public enum Status {
HAPPY("happy"), ANGRY("angry");
private String title;
public static Status findStatus( final String title) {
return stream( values() )
.filter( status-> status.getTitle().equals( title) )
.findFirst()
.orElseThrow( () -> new StatusNotFoundException( "...." ) );
}
}
And StatusNotFoundException extends RuntimeException {}
I registered this converter like this:
#Configuration
public class ConverterRegister implements WebMvcConfigurer {
#Override
public void addFormatters( FormatterRegistry registry ) {
registry.addConverter( new ConvertersContainer.StringToStatusConverter() );
WebMvcConfigurer.super.addFormatters( registry );
}
}
And the controllerAdvice:
#RestControllerAdvice
public class Advice {
#ExceptionHandler( StatusNotFoundException .class)
protected String handleStatusNotFoundException(StatusNotFoundException ex) { ... }
}
When I breakpoint the method Status#findStatus(...) is well executed but the exception is never catched in #RestControllerAdvice. What am I doing wrong?
Thanks a lot
It seems like Spring wrappes all exceptions thrown during the conversion's process and throws a org.springframework.beans.TypeMismatchException instead of user's custom exception.
In my mind this behavior is not normal, if an exception is thrown during the conversion, this exception should take precedence over all framework's exception. So to solve this issue, we have to extends ResponseEntityExceptionHandler and override its protected ResponseEntity<Object> handleTypeMismatch(...).
Thanks #Eric (the guy commented the question above).

Resources