Change Response status code using #ControllerAdvice and #ResponseStatus - java

I am using spring mvc, to handle excpetion i use global exception handler
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(value = HttpStatus.CONFLICT, reason = "Data integrity violation")
#ExceptionHandler({DataIntegrityViolationException.class})
public #ResponseBody AdminResponse handleConflict(DataIntegrityViolationException ex,HttpServletResponse httpServletResponse) {
AdminResponse error = new AdminResponse ();
httpServletResponse.setStatus(HttpStatus.CONFLICT.value());
error.setStatus(Status.FAILURE);
error.setErrorDescription(ex.getMessage());
return error;
}
as i know, the annotation #ResponseStatus(value = HttpStatus.CONFLICT will change the repose status code into HttpStatus.CONFLICT, but that is not happen.
when i created dummy exception and annotated this dummy exception with #ResponseStatus then throw this new exception, the GlobalControllerExceptionHandler catches and handle the exception and also changes the response status code.
how can i change the response status code without creating new Exception, i just need to catch DataIntegrityViolationException

You take to two way.
1. use #ResponseBody and return custom JSON String.
#ExceptionHandler(value = { HttpClientErrorException.class, HTTPException.class })
public #ResponseBody String checkHTTPException(HttpServletRequest req, Exception exception,
HttpServletResponse resp) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
CommonExceptionModel model = new CommonExceptionModel();
model.setMessage("400 Bad Request");
model.setCode(HttpStatus.BAD_REQUEST.toString());
String commonExceptionString = mapper.writeValueAsString(model);
return commonExceptionString;
}
2. use ResponseEntity and exception
Return ResponseEntity.
ResponseEntity.status(exception.getStatusCode()).headers(exception.getResponseHeaders())
.body(exception.getResponseBodyAsString());

Related

Custom exception handler for status NotFound

I want to implement custom exception handler for status NotFoundException for Spring Boot:
#ExceptionHandler({ AccessDeniedException.class, NotFoundException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final AccessDeniedException ex) {
......
}
I can't find what is the proper import for NotFoundException Do you know what exception what is the proper import for that case?
Either add an exception handler for a NoHandlerFoundException:
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponseDto> handle(NoHandlerFoundException e) {
// ...
}
Or have your controller advice extend ResponseEntityExceptionHandler and override the handleNoHandlerFoundException method.
By the way, your code snippet declares a handler for two different exceptions while the method parameter final AccessDeniedException ex explicitly expects an exception of type AccessDeniedException. I would suggest either declaring multiple handler methods or generalize the paramater to an Exception instead.
I agree with #Michiel on, method parameter(AccessDeniedException ex) should be parent class of below classes:
AccessDeniedException
NotFoundException
try this
#ExceptionHandler({ AccessDeniedException.class, NotFoundException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final **Exception** ex) {
......
}
i have used #ControllerAdvice like
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler({BadRequestException.class, IllegalArgumentException.class, MaxUploadSizeExceededException.class})
#ResponseBody
public ResponseEntity<ErrorResponse> handleBadRequestException(Exception exception, WebRequest request) {
String message = StringUtils.isEmpty(exception.getMessage()) ? properties.getGeneralMessages().get("fail") : exception.getMessage();
if (message.contains(";"))
message = message.substring(0, message.indexOf(";"));
return getResponseEntity(message, null);
}
}

Spring boot Rest responding with empty body for exceptions other than the ones overridden in my #ControllerAdvice

I have a #ControllerAdvice extending ResponseEntityExceptionHandler as an attempt for me to control standard response for any exception raised with in the API call workflow.
Without the Controller advice. I get HTML based generic response generated by spring with correct response headers. But when I add my #ControllerAdvice, Spring doesn't response with generic error body. The body is empty with correct response headers
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String erroMessage = "Required Parameter: '"+ex.getParameterName()+"' was not available in the request.";
TrsApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, erroMessage, ex, ApiErrorCode.INVALID_REQUEST);
return buildResponseEntity(apiError);
}
So, now in case of a required parameter missing in the request, the flow beautifully trigger my overridden implementation and responds with JSON payload describing the error. But, in case of any other exception like HttpMediaTypeNotAcceptableException, spring is responding with empty body.
Before I added my advice, spring was responding with generic error response. I am new to spring boot ecosystem. Need help in understanding if this is an expected behavior of if there is a better approach of achieving centralized error handling.
I guess that I found out a solution for swallowed body when ControllerAdvice class is extending ResponeEntityExceptionHandler. In my case the setup looks like that:
#ControllerAdvice
#Slf4j
class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
// logic that creates apiError object (object with status, message, errorCode, etc)
//...
return handleExceptionInternal(exception, apiError, headers, status, request);
}
And this worked like a charm for exceptions of class MethodArgumentNotValidException. But it broke all other exceptions handled by ResponseEntityExceptionHandler, and returned empty response body for them.
But the fix is easy, just override handleExceptionInternal from ResponseEntityExceptionHandler:
#ControllerAdvice
#Slf4j
class GlobalExceptionHandlers extends ResponseEntityExceptionHandler {
/// ... code from previous snippet
#Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception exception,
Object body,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
// for all exceptions that are not overriden, the body is null, so we can
// just provide new body based on error message and call super method
var apiError = Objects.isNull(body)
? new ApiError(status, exception.getMessage()) // <--
: body;
return super.handleExceptionInternal(exception, apiError, headers, status, request);
}
}
This is expected behavior.Look at the source code of the class ResponseEntityExceptionHandler.
#ExceptionHandler({
org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
All of these exceptions are handled WITHOUT the response body.
A common method is invoked :
//second parameter is body which is null
handleExceptionInternal(ex, null, headers, status, request)
If you need to handle specific exceptions differently, override them, example where I wanted to send a custom response for HttpMessageNotReadableException
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request)
{
logger.error("handleHttpMessageNotReadable()", ex);
ValidationErrors validationErrors = null;
if (ex.getRootCause() instanceof InvalidFormatException) {
InvalidFormatException jacksonDataBindInvalidFormatException = (InvalidFormatException) ex.getRootCause();
validationErrors = new ValidationErrors(jacksonDataBindInvalidFormatException.getOriginalMessage());
}
headers.add("X-Validation-Failure", "Request validation failed !");
return handleExceptionInternal(ex, validationErrors, headers, status, request);
}
You need to define generic exception structure once you use #ControllerAdvice.
#ResponseBody
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse generationExceptionHandler(Exception e){
log.info("Responding INTERNAL SERVER ERROR Exception");
return new ErrorResponse(ServiceException.getSystemError());
}

How to get all exception handlers annotated by #ExceptionHanlder?

How to get all exception handlers annotated by #ExceptionHanlder and I can call them manually?
Background
I need to handle some exceptions by my own exception handlers but in some situation my handled exceptions are not thrown directly by spring, and they are wrapped in the cause by. So I need to handle these caused by exceptions in one place using my own exception handling strategy in the existing #ExceptionHandlers. How can I do that?
Try to use Java Reflection Api to find classes annotated with "ExceptionHanlder". And invoke any method or whatever you want.
You can extend ResponseEntityExceptionHandler and make it a #ControllerAdvise like below.
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({YourException.class})
public ResponseEntity<Object> handleMyException(Exception ex, WebRequest request) {
... handle the way you like it
return new ResponseEntity<Object>(YourErrorObject, new HttpHeaders(), HttpStatus);
}
}
Spring provides #ControllerAdvice annotation that we can use with any class to define our global exception handler. The handler methods in Global Controller Advice is same as Controller based exception handler methods and used when controller class is not able to handle the exception.
You want to use exception handling strategy in your one place. that you can define multiple exception or make message using exception in exception controller.
like this :
#ExceptionHandler(value = { HttpClientErrorException.class, HTTPException.class, HttpMediaTypeException.class,
HttpMediaTypeNotSupportedException.class, HttpMessageNotReadableException.class })
or
#ExceptionHandler
#ResponseBody
ExceptionRepresentation handle(Exception exception) {
ExceptionRepresentation body = new ExceptionRepresentation(exception.getLocalizedMessage());
HttpStatus responseStatus = resolveAnnotatedResponseStatus(exception);
return new ResponseEntity<ExceptionRepresentation>(body, responseStatus);
}
HttpStatus resolveAnnotatedResponseStatus(Exception exception) {
ResponseStatus annotation = findMergedAnnotation(exception.getClass(), ResponseStatus.class);
if (annotation != null) {
return annotation.value();
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
Here is a work around. You can catch the the wrapping exception and then check the root cause of the exception. Here is an example of MySQLIntegrityConstraintViolationException which is wrapped by DataIntegrityViolationException in spring:
#ExceptionHandler(DataIntegrityViolationException.class)
#ResponseBody
public ResponseEntity<Object> proccessMySQLIntegrityConstraint(DataIntegrityViolationException exception) {
if (exception.getRootCause() instanceof MySQLIntegrityConstraintViolationException) {
doSomething....
} else {
doSomethingElse...
}
}

Catch a deserialization exception before a ControllerAdvice

Here is a problem: I have a controller that takes an input model. Lets say
public class AppUserUpdateData {
#NotNull
#Size(min = 1, max = 50)
protected String login;
#JsonDeserialize(using = MyDateTimeDeserializer.class)
protected Date startWorkDate;
*************
other properties and methods
*************
}
The problem is when I want to restrict a down board of a date I eventually get an HTTP exception 400 without any messages despite I handle this case in my code!
here is a controller:
#RequestMapping(
value = "/users/{userId}", method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public #ResponseBody AbstractSuccessResult updateUser(#PathVariable Long userId,
#RequestBody AppUserUpdateData appUserUpdateRequest, HttpServletRequest request) {
AbstractSuccessResult response = new AbstractSuccessResult();
appUserService.updateUser(appUserUpdateRequest, userId);
return response;
}
Here is a Deserializer:
public class MyDateTimeDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
try {
return DataTypeHelper.stringToDateTime(jsonParser.getText());
} catch (MyOwnWrittenException ex) {
throw ex;
}
}
}
In a DataTypeHelper.stringToDateTime are some validations that are blocking invalid date-strings.
And there is a handler for a my exception:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
The problem is that when an exception in a MyDateTimeDeserializer has been thrown it doesn't falling into a MyExceptionHandler but I cannot understand why? What am I doing wrong?
In the response is just an empty response with a code 400(
UPD
Thanks to #Joe Doe's answer the problem has been solved. Here is my updated handler:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ MyOwnWrittenException .class})
protected ResponseEntity<Object> handleInvalidRequest(RuntimeException exc,
WebRequest request) {
MyOwnWrittenException ex = (MyOwnWrittenException) exc;
BasicErrorMessage message; = new BasicErrorMessage(ex.getMessage());
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(message);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(exc, result, headers, HttpStatus.BAD_REQUEST, request);
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Throwable cause = ex.getCause();
String message = null;
if (cause instanceof JsonMappingException) {
if (cause.getCause() instanceof MyOwnWrittenException) {
return handleInvalidRequest((RuntimeException) cause.getCause(), request);
} else {
message = cause.getMessage();
}
} else {
message = ex.getMessage();
}
AbstractUnsuccessfulResult result = new AbstractUnsuccessfulResult(
new BasicErrorMessage(message));
headers.setContentType(MediaType.APPLICATION_JSON);
return handleExceptionInternal(ex, result, headers, HttpStatus.BAD_REQUEST, request);
}
}
UPD
In my project it doesn't work without annotation #Order(Ordered.HIGHEST_PRECEDENCE)
I believe that is because of number of ControllerAdvices in a project
Before updateUser in your controller gets invoked, its arguments have to be resolved. This is where HandlerMethodArgumentResolverComposite comes in, and delegates to one of pre-registered HandlerMethodArgumentResolvers - in this particular case it delegates to RequestResponseBodyMethodProcessor.
By delegating I mean calling the resolver's resolveArgument method. This method indirectly calls the deserialize method from your deserializer, which throws an exception of type MyOwnWrittenException. The problem is that this exception gets wrapped in another exception. In fact, by the time it propagates back to resolveArgument, it's of type HttpMessageNotReadableException.
So, rather than catching MyOwnWrittenException in your custom exception handler, you need to catch exceptions of type HttpMessageNotReadableException. Then, in the method that handles that case, you can check whether the "original" exception was in fact MyOwnWrittenException - you can do that by repeatedly calling the getCause method. In my case (it's probably going to be the same in yours), I needed to call getCause twice to "unwrap" the original exception (HttpMessageNotReadableException -> JsonMappingException -> MyOwnWrittenException).
Note that you can't simply substitute MyOwnWrittenException with HttpMessageNotReadableException in your exception handler since it clashes (at runtime) with another method, specifically designed to handle exceptions of the latter type, called handleHttpMessageNotReadable.
In summary, you can do something like this:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// ex.getCause().getCause().getClass() gives MyOwnWrittenException
// the actual logic that handles the exception...
}
}

How to catch all unhandled exceptions (i.e. without existing #ExceptionHandler) in Spring MVC?

I want to let HandlerExceptionResolver resolve any Exceptions that I don't explicit catch via #ExceptionHandler annotation.
Anyways, I want to apply specific logic on those exceptions. Eg send a mail notification or log additionally. I can achieve this by adding a #ExceptionHandler(Exception.class) catch as follows:
#RestControllerAdvice
public MyExceptionHandler {
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Object io(HttpServletRequest req, Exception e) {
return ...
}
#ExceptionHandler(Exception.class)
public Object exception(HttpServletRequest req, Exception e) {
MailService.send();
Logger.logInSpecificWay();
//TODO how to continue in the "normal" spring way with HandlerExceptionResolver?
}
}
Problem: if I add #ExceptionHandler(Exception.class) like that, I can catch those unhandled exceptions.
BUT I cannot let spring continue the normal workflow with HandlerExceptionResolver to create the response ModelAndView and set a HTTP STATUS code automatically.
Eg if someone tries a POST on a GET method, spring by default would return a 405 Method not allowed. But with an #ExceptionHandler(Exception.class) I would swallow this standard handling of spring...
So how can I keep the default HandlerExceptionResolver, but still apply my custom logic?
To provide a complete solution: it works just by extending ResponseEntityExceptionHandler, as that handles all the spring-mvc errors.
And the ones not handled can then be caught using #ExceptionHandler(Exception.class).
#RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
MailService.send();
Logger.logInSpecificWay();
return ... custom exception
}
}
Well, I was facing the same problem some time back and have tried several ways like extending ResponseEntityExceptionHandler but all them were solving some problems but creating other ones.
Then I have decided to go with a custom solution which was also allowing me to send additional information and I have written below code
#RestControllerAdvice
public class MyExceptionHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<Object> handleNumberFormatException(NumberFormatException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, "Please enter a valid value"), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, ex.getMessage()), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex) {
return new ResponseEntity<>(getBody(FORBIDDEN, ex, ex.getMessage()), new HttpHeaders(), FORBIDDEN);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
return new ResponseEntity<>(getBody(INTERNAL_SERVER_ERROR, ex, "Something Went Wrong"), new HttpHeaders(), INTERNAL_SERVER_ERROR);
}
public Map<String, Object> getBody(HttpStatus status, Exception ex, String message) {
log.error(message, ex);
Map<String, Object> body = new LinkedHashMap<>();
body.put("message", message);
body.put("timestamp", new Date());
body.put("status", status.value());
body.put("error", status.getReasonPhrase());
body.put("exception", ex.toString());
Throwable cause = ex.getCause();
if (cause != null) {
body.put("exceptionCause", ex.getCause().toString());
}
return body;
}
}
Create classes for exception handling in this way
#RestControllerAdvice
public class MyExceptionHandler extends BaseExceptionHandler {
}
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {
}
Here ResponseEntityExceptionHandler is provided by spring and override the several exception handler methods provided by it related to the requestMethodNotSupported,missingPathVariable,noHandlerFound,typeMismatch,asyncRequestTimeouts ....... with your own exception messages or error response objects and status codes
and have a method with #ExceptionHandler(Exception.class) in MyExceptionHandler where the thrown exception comes finally if it doesn't have a matching handler.
I had the same issue and solved it creating a implementation of the interface HandlerExceptionResolver and removing the generic #ExceptionHandler(Exception.class) from the generic handler method.
.
It works this way:
Spring will try to handle the exception calling MyExceptionHandler first, but it will fail to find a handler because the annotation was removed from the generic handler. Next it will try other implementations of the interface HandlerExceptionResolver. It will enter this generic implementation that just delegates to the original generic error handler.
After that, I need to convert the ResponseEntity response to ModelAndView using MappingJackson2JsonView because this interface expects a ModelAndView as return type.
#Component
class GenericErrorHandler(
private val errorHandler: MyExceptionHandler,
private val objectMapper: ObjectMapper
) : HandlerExceptionResolver {
override fun resolveException(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception): ModelAndView? {
// handle exception
val responseEntity = errorHandler.handleUnexpectedException(ex)
// prepare JSON view
val jsonView = MappingJackson2JsonView(objectMapper)
jsonView.setExtractValueFromSingleKeyModel(true) // prevents creating the body key in the response json
// prepare ModelAndView
val mv = ModelAndView(jsonView, mapOf("body" to responseEntity.body))
mv.status = responseEntity.statusCode
mv.view = jsonView
return mv
}
}

Categories

Resources