ControllerAdvice not work correctly in ConstraintValidator - spring

I have a custom validation for amount property in request body
and if failed validation its throw my BadRequestException (extend from my BaseException)
and in ExceptionTranslator class (marked with #ControllerAdvice) i have two method for catch exceptions, one for BaseException and other one for RuntimeException
my problem is when validation failed i expect handleBaseException invoked but handleRuntimeException invoked with message Unexpected exception during isValid call.
#Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
if (value < appConfig.getMinAmount()) {
throw new BadRequestException()
.addErrorDetail(null, I18nKey.EXCEPTION.AMOUNT_SIZE, appConfig.getMinAmount());
}
return true;
}
public class BadRequestException extends BaseException {
public BadRequestException() {
super(HttpStatus.BAD_REQUEST);
}
}
#ControllerAdvice
public class ExceptionTranslator {
#ExceptionHandler(value = {BaseException.class})
public ResponseEntity<Object> handleBaseException(BaseException ex) {
//Some work
}
#ExceptionHandler(value = {RuntimeException.class})
protected ResponseEntity<Object> handleRuntimeException(RuntimeException ex) {
//Some work
}
}

There are many situations where you can apply a custom validation correctly:
When dealing with DTO validation, you must annotate the request body received in the request with #Validated.
On the custom validator, the thrown exception will be MethodArgumentNotValidException if some constraints violated (verifications on some fields) or an exception ConstraintViolationException on your case, this is the exception that will be thrown not the BaseException.
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> onConstraintValidationException(
final MethodArgumentNotValidException exception, HttpServletRequest request) {...}
So, the BaseException will be within a ConstraintViolationException catched by the controller at the point :
#ExceptionHandler(value = {RuntimeException.class})

Related

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

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.

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()

Make Spring's #RequestBody annotation return a custom response on-fail

I have a controller as follows:
public void createEntity(#Valid #RequestBody final MyEntity myEntity) {}
However, when the object transformation fails, the API automatically returns a 400 with the Java stack trace. How can I modify this on-failure response? (I wish to change or remove the response message).
Here is an example how to do this with an #ExceptionHandler annotation
#RestController
public class Controller {
#RequestMapping(value = "/", method = RequestMethod.POST)
public void createEntity(#Valid #RequestBody final MyEntity myEntity) {
//
}
#ControllerAdvice
public class RestEndpointExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleNotValidExceptionException(HttpServletRequest req, MethodArgumentNotValidException ex) {
Object customException = "Validation failed";
return new ResponseEntity<Object>(customException, HttpStatus.BAD_REQUEST);
}
}
}
I pushed the code in here
You can use #ExceptionHandler with #ResponseStatus and leave handler empty so that only Status Code is returned back.
#ExceptionHandler(EmptyResultDataAccessException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public void notFoundException() {
}

Spring validation returns long error messages, not just the customized message

Spring validation returns long error message instead of the customized once.
This is the section of code in the dto.
public class RequestDto implements Serializable {
#NotNull(message="{id.required}")
private Long id;
}
In controller added the #Valid for input.
#RequestMapping(value = ApiPath.PATH, method = RequestMethod.POST, produces = { "application/xml",
"application/json" })
public #ResponseBody ResultDecorator saveRequest(
#Valid #RequestBody RequestDto msaDisabScreenRequestDto) throws Exception {
}
API returns the following error.
<message>Validation failed for argument at index 0 in method: public om.gov.moh.msa.framework.resolver.ResultDecorator om.controller.MaController.saveRequest(om..dto.RequestDto) throws java.lang.Exception, with 1 error(s): [Field error in object 'requestDto' on field 'id': rejected value [null]; codes [NotNull.requestDto.id,NotNull.id,NotNull.java.lang.Long,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requestDto.id,id]; arguments []; default message [civilId]]; **default message [ID is required.]]** </message>
Here the custom message is present at the end. (default message [ID is required.)
Using Controller advice for global exception and I'm overriding handleMethodArgumentNotValid. How can I return only the custom message here?
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
}
You can get default/custom message like result.getFieldError("yourFieldName").getDefaultMessage()
You can catch error messages either through controller method which should look like this
#RequestMapping(value = ApiPath.PATH, method = RequestMethod.POST, produces = { "application/xml", "application/json" })
public #ResponseBody ResultDecorator saveRequest(#Valid #RequestBody RequestDto msaDisabScreenRequestDto, BindingResult result) throws Exception {
if(result.hasErrors()){
String errorMessage = result.getFieldError("yourFieldName").getDefaultMessage();
}
}
Or through Global Exception handler
Updated
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
//New Code
BindingResult bindingResult = ex.getBindingResult();
String errorMessage = result.getFieldError("yourFieldName").getDefaultMessage();
//---------------
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
apiError.setMessage(errorMessage);
return buildResponseEntity(apiError);
}
}
Thanks Afridi,
Created a string buffer and added all the error messages into that.
/**
* Spring validation related exception
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
final StringBuffer errors = new StringBuffer();
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
for (final FieldError error : ex.getBindingResult().getFieldErrors()) {
errors.append("\n");
errors.append(error.getField() + ": " + error.getDefaultMessage());
}
apiError.setMessage(errors.toString());
return buildResponseEntity(apiError);
}
As Afridi said in #ControllerAdvice can do this also:
#ExceptionHandler(value = MethodArgumentNotValidException.class)
#SuppressWarnings("unchecked")
#ResponseBody
public Result methodArgumentNotValidExceptionHandler(HttpServletRequest req, HttpServletResponse response, MethodArgumentNotValidException e) throws IOException {
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
// todo return to your custom result
}
There are two point :
Exception class is MethodArgumentNotValidException
The first Error getDefaultMessage() can get your custom message in Annotation
In WebFlux :
Handle WebExchangeBindException for customising the default error message of #Valid
#ControllerAdvice
public class ValidationHandler {
#ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<List<String>> handleException(WebExchangeBindException e) {
var errors = e.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
}
Reference : https://www.vinsguru.com/spring-webflux-validation/

Setting the status code message of a HTTP ResponseCode thrown by an #ResponseCode annotate Exception

I am currently trying to set the message of a HTTP Status Code thrown by an #ResponseCode annotated Exception.
I have defined the exception:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public final class BadRequestException extends IllegalArgumentException {
/**
* The serial version UID.
*/
private static final long serialVersionUID = -6121234578480284282L;
public BadRequestException() {
super();
}
public BadRequestException(String message, Throwable cause) {
super(message, cause);
}
public BadRequestException(String message) {
super(message);
}
public BadRequestException(Throwable cause) {
super(cause);
}
}
If the exception is thrown I throw it again in my #ExceptionHandler annotate method:
#ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(Exception e, HttpSession session) {
if (e instanceof BadRequestException) {
throw (BadRequestException)e;
}
return FAILURE;
}
I generally throw this exception in this way:
if (result.hasErrors()) {
throw new BadRequestException("Bad Request Message.");
}
The HTTP Status Code always returns only "HTTP Status 400 -" without a message is set.
THX!
Annotate your exception handler with the #ResponseStatus. Then created a basic error/exception view and pass the exception stack trace or whatever to that view

Resources