I'm facing an issue with Spring (and kotlin?), where my global error handlers do not catch any exceptions thrown within a custom converter.
I know spring supports string->UUID mapping by default, but I wanted to explicitly check if an exception is actually thrown. Which it is the following converter. The behaviour is the same with and without my own implementation of the converter.
My WebMvcConfuguration looks as follows:
#Configuration
class WebMvcConfiguration : WebMvcConfigurerAdapter() {
override fun addFormatters(registry: FormatterRegistry) {
super.addFormatters(registry)
registry.addConverter(Converter<String, UUID> { str ->
try {
UUID.fromString(str)
} catch(e: IllegalArgumentException){
throw RuntimeException(e)
}
})
}
And this is my GlobalExceptionHandler:
(it also contains other handlers, which I ommitted for brevity)
#ControllerAdvice
class GlobalExceptionHandler : ResponseEntityExceptionHandler() {
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
fun handleException(ex: Exception): ApiError {
logger.info(ex.message, ex)
return ApiError(ex.message)
}
}
And finally, the controller:
#Controller
class MyController : ApiBaseController() {
#GetMapping("/something/{id}")
fun getSomething(#PathVariable("id") id: UUID) {
throw NotImplementedError()
}
}
Exceptions inside controller (for example the NotImplementedError) methods are caught just fine. But the IllegalArgumentException thrown within the converter when invalid UUIDs are passed is swallowed, and spring returns an empty 400 response.
My question now is: How do I catch these errors and respond with a custom error message?
Thanks in advance!
I had the same problem. Spring swallowed any IllegalArgumentException (ConversionFailedException in my case).
To get the behavior i was looking for; i.e. only handling the listed exceptions and using default behavior for the other ones, you must not extend the ResponseEntityExceptionHandler.
Example:
#ControllerAdvice
public class RestResponseEntityExceptionHandler{
#ExceptionHandler(value = {NotFoundException.class})
public ResponseEntity<Object> handleNotFound(NotFoundException e, WebRequest request){
return new ResponseEntity<>(e.getMessage(), new HttpHeaders(), HttpStatus.NOT_FOUND);
}
}
I checked the solution from #georg-moser. At first, it looks good, but it looks it contains another issue. It translates all exceptions to the HTTP code of 500, which is something one not always wants.
Instead, I decided to overwrite the handleExceptionInternal method from the ResponseEntityExceptionHandler.
In my case logging the error was enough, so I ended up with the following:
#Override
#NonNull
protected ResponseEntity<Object> handleExceptionInternal(#Nonnull final Exception e,
final Object body,
final HttpHeaders headers,
final HttpStatus status,
#Nonnull final WebRequest request) {
final ResponseEntity<Object> responseEntity = super.handleExceptionInternal(e, body, headers, status, request);
logGenericException(e);
return responseEntity;
}
I hope it helps!
After some more trial and error, I have found a solution:
Instead of using #ControllerAdvice, implementing a BaseController that others inherit from and adding the exception handlers there works.
So my Base controller looks like this:
abstract class ApiBaseController{
#ExceptionHandler(Exception::class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
fun handleException(ex: Exception): ApiError {
return ApiError(ex.message)
}
}
If anyone can elaborate on why it works like this and not the other way, please do so and I will mark your answer as accepted.
Related
im working ins Spring web flux project and I used functional endpoints instead of controller annotation but I didn't find a solution to handle multiple exceptions for the same endpoint , this is my code :
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.GET("/router/users/{id}"),this::renderException);
}
private Mono<ServerResponse> renderException(ServerRequest request) {
Map<String, Object> error = this.getErrorAttributes(request, ErrorAttributeOptions.defaults());
error.remove("status");
error.remove("requestId");
return ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error));
}
for the endpoint /router/users/{id} i trigger UserNotFoundException and UserException and I want to return 404 for UserNotFoundException and 500 for UserException but I don't know how to do that in the functional endpoint. anyone can guide me on how to do this in the correct way like we did in using #ExceptionHandler in rest controller?
If returning proper code is all you care about then adding #ResponseStatus for your custom exceptions might be the best solution.
#ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
// more methods...
public UserNotFoundException(final String message) {
super(message);
}
}
But if you want to build ServerResponse by yourself, make use of project reactor operators, like switchIfEmpty() or onErrorMap(), etc. which could be used as following
Mono<ServerResponse> response() {
return exampleService.getUser()
.flatMap(user -> ServerResponse.ok().body(user, User.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
You might want to take a look at docs Which operator do I need? - handling errors
Trying to save 2 times the same barcode card the method saveBarcodecard (left side image) throw the error: SQLIntegrityConstraintViolationException: "Duplicate entry '1-*****-EAN13' for key 'fcs_barcodecards.UK4ComplexKey" that I was expected to be intercepted by the method: handleExceptionInternal meant to handler ALL the Exceptions that are not specifically custom implemented but not.
Please note that:
SQLIntegrityConstraintViolationException extends ... extends ... Exception
I need to implement a custom handler handleSQLIntegrityConstraintViolationException (please see the image 2 below -green-) to solve this issue.
My simple question is: how come this? :)
Many thanks in advance for your answers, if any :)
#Service
public class FcsBarcodecardServiceImpl implements FcsBarcodecardService {
#Autowired
FcsBarcodecardRepository fcsBarcodecardRepository;
#Autowired
private FcsClientRepository fcsClientRepository;
#Autowired
private FcsBarcodecardMapper fcsBarcodecardMapper;
#Override
public FcsBarcodecardResponse saveBarcodecard(Long clientId, FcsBarcodecard fcsBarcodecard) {
Optional<FcsClient> fcsClient = fcsClientRepository.findById(clientId);
if (!fcsClient.isPresent()) {
throw new UsernameNotFoundException("This client do not exists!");
}
fcsBarcodecard.setFcsClient(fcsClient.get());
return fcsBarcodecardMapper
.fromFcsBarcodecardToFcsBarcodecardResponse(
fcsBarcodecardRepository.save(fcsBarcodecard));
}
}
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
private static final String VALIDATION_ERROR_CHECK_ERRORS_FIELD_FOR_DETAILS = "Validation error. Check 'errors' field for details.";
/**
* Predefined: A single place to customize the response body of all exception
* types.
*/
#Override
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<Object> handleExceptionInternal(Exception exception, Object body, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return buildErrorResponse(exception, FCS_EALLTYPES500, status, request);
}
#ExceptionHandler(SQLIntegrityConstraintViolationException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<Object> handleSQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException ex,
WebRequest request) {
return buildErrorResponse(ex, FCS_EALLTYPES500, HttpStatus.INTERNAL_SERVER_ERROR, request);
}
ResponseEntityExceptionHandler#handleExceptionInternal() is a place to customize the response body of only those exception types that are handled by an #ExceptionHandler defined in ResponseEntityExceptionHandler. As of the latest Spring version those are
#ExceptionHandler({
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
})
Obviously your SQLIntegrityConstraintViolationException is not among them and requires a separate exception handler to be caught and processed by.
If you'd like to have a single place to handle any expection, you'd have to specifically define an exception handler with e.g. #ExceptionHandler(Exception.class)
When an item that doesn't exist in my web app is invoked through an URL, Spring responds with a JSON with data like (timestand, status, error, message, path). So, I need to change the structure of this JSON, specificly I need to remove path.
How can I do it?
Where should I implement the customization of the exception in my project?
Best regards to everyone!
Json response to modify
It's pretty easy in Spring MVC applications to handle errors by their types using the #ContollerAdvice class.
You could define your own handler for the exceptions you get on a method calls.
E.g.:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(value = ExceptionToHandle.class)
#ResponseBody
public YourResponse handle(ExceptionToHandle ex) {
return new YourResponse(ex.getMessage());
}
}
Here YourResponse is just a POJO, that could have any structure your want to be presented at the client.
The #ExceptionHandler specifies what types of errors will be handled in the method (including more specific types).
The #ResponseBody says that your returned value will be presented in the JSON format in your response.
You may try something like that:
#RestController
#RequestMapping("/")
public class TestController {
#GetMapping("/exception")
void getException() {
throw new MyCustomException("Something went wrong!");
}
class MyCustomException extends RuntimeException {
MyCustomException(String message) {
super(message);
}
}
class CustomError {
private String message;
private Integer code;
CustomError(String message, Integer code) {
this.message = message;
this.code = code;
}
public String getMessage() {
return message;
}
public Integer getCode() {
return code;
}
}
#ExceptionHandler(MyCustomException.class)
public CustomError handleMyCustomException(Exception ex) {
return new CustomError("Oops! " + ex.getMessage(), HttpStatus.BAD_REQUEST.value());
}
}
Fast and simple, you can just make your own exception and your own error object (which is later turned to json).
If you ask where to put such a thing... Well, you can make a separate class for the exception (and an exception package also), and put a small #ExceptionHandler method inside your controller. If you don't want it to be in the same class, you may delegate it to separate class also; for further and in-depth reading, look up for annotation like #ControllerAdvice.
I'm using Spring to create an API, but I'm having some trouble introducing custom error reporting on (a part of) the validation of the request body.
When parsing/validation errors occur, I want to give a custom response back to the user.
This works well for fields annotated with #Valid along with validators like #javax.validation.constraints.NotNull by using a custom ResponseEntityExceptionHandler annotated with #ControllerAdvice.
It does not work however if an Exception is thrown while parsing the request body (before the validations even run). In that case I get an html error page with status 500 (Server Error)
How can I make sure the exceptions during parsing lead to the same kind of response as the (custom) one I return for validation failures?
My endpoint's code looks like this:
#RequestMapping(value= "/endpoint"
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(#Valid #RequestBody MyRequestBody requestData){
// ...
}
MyRequestBody class looks like this:
#Validated
public class MyRequestData {
#JsonProperty("stringValue")
private String stringValue = null;
#NotNull
#Valid
public String getStringValue() {
return stringValue;
}
// ...
public enum EnumValueEnum {
VALUE_1("value 1"),
VALUE_1("value 2");
private String value;
EnumValueEnum(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static EnumValueEnum fromValue(String text) {
if(text == null){
return null;
}
for (EnumValueEnum b : EnumValueEnum.values()){
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
}
}
#JsonProperty("enumValue")
private EnumValueEnum enumValue = null;
}
The custom validation error handling (and reporting) looks like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
}
}
In this code, if a user sends a request with an enum value that doesn't exist, an HttpMessageNotReadableException is thrown. I would like to catch that somewhere and replace it with a custom response that is consistent with the other exception handling I do. Where/How can I do that?
I found a solution to my own problem.
You can actually use Spring MVC's normal exception handling:
Annotating a method with #ExceptionHandler will make Spring try to use it for exception handling for the exception type specified (in the annotation's value field or the method's argument). This method can be placed in the controller or even in the ResponseEntityExceptionHandler I use for the other validation response handling.
#ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}
Mind which type of exception you handle:
The catch here was that the exception thrown while parsing is wrapped in (some subtype of) a JsonMappingException which in turn is wrapped again in a HttpMessageConversionException.
e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception
The #ExceptionHandler should therefor accept HttpMessageConversionException instead of the originally thrown exception (which in my case was HttpMessageNotReadableException)
It will not work if you write an #ExceptionHandler that only accepts your original Exception!
I have implemented a Spring Rest Controller that streams back large files using the StreamingResponseBody. However, these files are coming from another system and there is the potential for something to go wrong while streaming them back. When this occurs I am throwing a custom Exception (MyException). I am handling the exception in an #ExceptionHandler implementation which is below. I am attempting to set the response httpstatus and error message but I am always receiving http status 406. What is the proper way to handle errors/exceptions while returning a StreamingResponseBody?
#ExceptionHandler(MyException.class)
public void handleParsException( MyException exception, HttpServletResponse response) throws IOException
{
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(),exception.getMessage());
}
You should handle all errors in the same way. There are many options.
I prefer next:
Controller Advice
It is a good idea to have an entity to send a generic error response, an example:
public class Error {
private String code;
private int status;
private String message;
// Getters and Setters
}
Otherwise, to handle exceptions you should create a class annotated with #ControllerAdvice and then create methods annotated with #ExceptionHandler and the exception or exceptions (it could be more than one) you want to handle. Finally return ResponseEntity<Error> with the status code you want.
public class Hanlder{
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleResourceNotFoundException(MyException
myException, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.CONFLICT.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.CONFLICT);
}
#ExceptionHandler({DataAccessException.class, , OtherException.class})
public ResponseEntity<?> handleResourceNotFoundException(Exception
exception, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.INTERNAL_ERROR.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.INTERNAL_ERROR);
}
}
Other ways:
Annotate exception directly
Other way is annotating directly the excetion with the status and the reason to return:
#ResponseStatus(value=HttpStatus.CONFLICT, reason="Error with StreamingResponseBody")
public class MyError extends RuntimeException {
// Impl ...
}
Exception Handler in a specific controller
Use a method annotated with #ExceptionHandler in a method of a #Controller to handle #RequestMapping exceptions:
#ResponseStatus(value=HttpStatus.CONFLICT,
reason="Error with StreamingResponse Body")
#ExceptionHandler(MyError.class)
public void entitiyExists() {
}
I figured the problem out. The client was only accepting the file type as an acceptable response. Therefore, when returning an error in the form of an html page I was getting httpstatus 406. I just needed to tell the client to accept html as well to display the message.