I have custom ConstraintValidator which checks #RequestParam for valid values.
I also have hibernate entities with some attributes annotated with #NotNull.
Default behavior is that ConstraintViolationException is translated to HttpStatus=500.
This is desired for error raising from hibernate (that is persisting entities and so).
But I would like to raise HttpStatus=400 for ConstraintViolations raised from #RestController.
#ExceptionHandler works but it cannot distinguish between violations raised from hibernate and spring layer. I guess both layers uses hibernate validator dependency but we should be able to distinguish among those.
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
return getObjectResponseEntity(ex, request, HttpStatus.BAD_REQUEST);
}
I wonder if there are some other ways to achieve this. I have tried #InitBinder validator but it seems not to work with #RequestParam.
Do you have any ideas? Thanks.
One solution for this is to list all violations from the given exception and check if the root bean is names like *Controller but this is not quite clean solution and bound a developer to name controllers this way.
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, WebRequest request) {
// TODO ugly
boolean isFromController = ex.getConstraintViolations().stream()
.map(cv -> cv.getRootBeanClass().getSimpleName())
.anyMatch(cv -> cv.contains("Controller"));
return getObjectResponseEntity(ex, request, isFromController ? HttpStatus.BAD_REQUEST : HttpStatus.INTERNAL_SERVER_ERROR);
}
The controller looks like this with the #SupportedFileTypes custom validator which checks string value against internal enum.
#GetMapping(value = "/{requirementId}/export-cisel", produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public CisRadDto exportRequirementNumbers(
#PathVariable UUID requirementId,
#SupportedFileTypes #RequestParam(value = "formatSouboru", required = false) String fileFormat
) {
return pozadavekFacade.exportRequirementNumbers(requirementId, fileFormat != null ? fileFormat : "default");
}
pozadavekFacade only saves the created entity which has the folowing check:
#NotBlank
#Column(length = 10, nullable = false)
private String idUzivatele;
As your code is spread across different layers you should be able to catch ConstraintViolationException in each of them.
So in your facade you can catch it and rethrow them as RuntimeException (or whatever the exception you want). Then your controller layer will map this exception to 500 internal error.
And in your controller layer keep your logic and translate ConstraintViolationException to a 400 bad request response.
Updated : you're not forced to add try-catch boilerplate code everywhere.
Example : (don't copy paste this code. It is just to demonstrate how you can get rid of try-catch block at multiple places)
class Service {
String a() {throw new RuntimeException();}
String b() {throw new RuntimeException();}
String c() {throw new RuntimeException();}
}
class Facade {
Service service;
String a() {return tryRun(() -> service.a());}
String b() {return tryRun(() -> service.b());}
String c() {return tryRun(() -> service.c());}
String tryRun(Supplier<String> run) {
try {
return run.get();
}
catch(ConstraintViolationException e) {
throw new RuntimeException(""); // don't forget to extract useful information from ConstraintViolationEx type.
}
}
}
Related
I've a file upload validation that raises a BindException instead of a MethodArgumentNotValidException and I don't understand why.
org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'patientProfileImageDTO' on field 'profileImage': rejected value [org.springframework.web.multipart.commons.CommonsMultipartFile#2840a305]; codes [CheckImageFormat.patientProfileImageDTO.profileImage,CheckImageFormat.profileImage,CheckImageFormat.org.springframework.web.multipart.MultipartFile,CheckImageFormat]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patientProfileImageDTO.profileImage,profileImage]; arguments []; default message [profileImage]]; default message [Invalid image format (allowed: png, jpg, jpeg)]
My Controller is:
#PostMapping("/patient/image")
public ResponseEntity<?> updateProfileImage(#Validated PatientProfileImageDTO patientProfileImageDTO)
and this is the PatientProfileImageDTO
public class PatientProfileImageDTO {
#CheckImageFormat
#CheckImageSize
private MultipartFile profileImage;
public MultipartFile getProfileImage() {
return profileImage;
}
public void setProfileImage(MultipartFile profileImage) {
this.profileImage = profileImage;
}
}
the CheckFormatImage and CheckImageSize validators are correctly invoked.
I need to catch these errors in my:
#ControllerAdvice
public class ApiExceptionHandler {
ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, WebRequest request) {
...
}
}
I've other custom validation annotations in another part of my code and they work as intended.
I mean:
#OldPasswordMatch(message = "old password mismatch")
private String oldPassword;
This custom validation triggers a MethodArgumentNotValidException that what I want.
What's wrong with my code?
Thanks.
There is also a BindException thrown by Spring MVC if an invalid object was created from the request parameters. MethodArgumentNotValidException is already a subclass of BindException.
These are actually intentionally different exceptions. #ModelAttribute, which is assumed by default if no other annotation is present, goes through data binding and validation, and raises BindException to indicate a failure with binding request properties or validating the resulting values. #RequestBody, on the other hand converts the body of the request via other converter, validates it and raises various conversion related exceptions or a MethodArgumentNotValidException if validation fails. In most cases a MethodArgumentNotValidException can be handled generically (e.g. via #ExceptionHandler method) while BindException is very often handled individually in each controller method.
You can process these errors separately or you can catch only the super class BindException.
#ExceptionHandler(BindException.class)
protected ResponseEntity<Object> handleBindException(BindException ex) {
// ..
}
I'm working on a Rest API. Currently, I'm trying to handle some wrong client input. So far so good, I have it working with java validation.
There is an specific case where I may have an odd client input for dates. Here's a mvce:
public class Input {
#NotNull(message = "Usage to should have a valid date")
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime someDateTime;
//getters and setters
}
public class Output {
//content not relevant for the case...
}
#RestController
public class FooController {
#PostMapping(path="/url", consumes = APPLICATION_JSON_UTF8_VALUE, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Output> foo(#Valid #ResponseBody Input input) {
//implementation details...
}
}
When testing it, this input works ok:
{
"someDateTime": "2019-01-01 00:00:00"
}
And this doesn't (time is missing):
{
"someDateTime": "2019-01-01"
}
For the latest input sample, I get an HttpMessageNotReadableException (from Spring).
I was trying to solve this by adding a method in a global exception handler:
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> customSpringInputHandler(
HttpMessageNotReadableException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(CLIENT_INPUT_EXCEPTION, e.getMessage()));
}
}
With this, now I get a more detailed exception message. Sadly, this message is too verbose to return to the clients of this API because it depicts some of the internal design of the application.
I can see this HttpMessageNotReadableException wraps an InvalidFormatException (from Jackson) that's better for me to use. Thing is, handling specific cases of the case of HttpMessageNotReadableException in a single method seems kinda fishy. I was thinking on create a specific exception handler for InvalidFormatException and then delegate that execution from this method. Now I want to know how Spring can provide me those handlers and do the proper delegation.
I had something built in JAX-RS in the past, it was like this:
#SuppressWarnings("unchecked")
protected <T extends Throwable> Response callChainedMapper(T wrappedException, Exception baseException) {
Response response;
ExceptionMapper<T> mapper = (ExceptionMapper<T>) providers.getExceptionMapper(wrappedException.getClass());
//no mapper could be found, so treat it as a generic exception
if (mapper == null) {
ExceptionMapper<Exception> generalExceptionMapper = (ExceptionMapper<Exception>) providers
.getExceptionMapper(Exception.class);
response = generalExceptionMapper.toResponse(baseException);
} else {
response = mapper.toResponse(wrappedException);
}
return response;
}
Can I do something similar in Spring? Or maybe is there another alternative ?
Not aware Spring provides specific feature for such delegation. But you can simply
do it by yourself by the following pattern. The ResponseEntityExceptionHandler provided by Spring also use this kind of pattern.
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({
HttpMessageNotReadableException.class ,
InvalidFormatException.class})
public ResponseEntity<ErrorResponse> customSpringInputHandler(Exception ex) {
if(ex instanceof HttpMessageNotReadableException){
return handle((HttpMessageNotReadableException) ex);
}else if(ex instance of InvalidFormatException) {
return handle((InvalidFormatException) ex);
}else{
throw ex;
}
}
private ResponseEntity<ErrorResponse> handle(HttpMessageNotReadableException ex){
//get the wrapped InvalidFormatException and call handle(invalidFormatException)
}
private ResponseEntity<ErrorResponse> handle(InvalidFormatException ex){
}
}
My requirement is to get the json response with customized error message when a required #RequestParam is not sent to the request handler or invalid parameter(required is int but user is passing string) is sent to the request handler.
currently I am trying to use the #Exceptionhandler mechanism to handle these exceptions. But the respective exception handler methods not getting invoked.
Please see the code snippet:
#Controller
#RequestMapping("api/v1/getDetails")
public class Abc {
#RequestMapping
#ResponseBody
public Envelope<Object> retrieveTransactions(#RequestParam(required = false) Integer a,
#RequestParam int b, #RequestParam(required = false) boolean c,
HttpServletRequest req) {`
//implementation goes here
}
#ExceptionHandler(MissingServletRequestParameterException.class)
#ResponseBody
public Envelope<Object> missingParameterExceptionHandler(Exception exception,
HttpServletRequest request) {
Envelope<Object> envelope = null;
//error implementation
return envelope;
}
#ExceptionHandler(TypeMismatchException.class)
#ResponseBody
public Envelope<Object> typeMismatchExpcetionHandler(Exception exception, HttpServletRequest request) {
Envelope<Object> envelope = null;
//error implementation
return envelope;
}
Do I need to configure anything extra for exception handler? can anyone tell me where I am doing the wrong.
Consider identifying the parameter name in the RequestParameter annotation.
For example
#RequestParam(value="blammy", required=false)
I've never bothered figuring out how to handle type mismatch,
instead I've found it easier to accept all parameters as String and perform all verification myself (including type).
Also,
If you are accepting the HttpServletRequest as a parameter to your handler,
then there is no need to use #RequestParam annotations,
just get the parameter values directly from the request.
Finally,
consider org.springframework.web.context.request.WebRequest
or org.springframework.web.context.request.NativeWebRequest
instead of HttpServletRequest.
Have you tried to use MethodArgumentNotValidException or HttpMessageNotReadableException instead on your handlers?
And put required = true on your #RequestParam declaration to catch missing params exceptions
#RequestParam(required = true)
I'm running a webapp in Spring Web MVC 3.0 and I have a number of controller methods whose signatures are roughly as follows:
#RequestMapping(value = "/{level1}/{level2}/foo", method = RequestMethod.POST)
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#RequestParam("foo_name") String fooname,
#RequestParam(value = "description", required = false) String description);
I'd like to add some validation - for example, description should be limited to a certain length or fooname should only contain certain characters. If this validation fails, I want to return a message to the user rather than just throw some unchecked exception (which would happen anyway if I let the data percolate down to the DAO layer). I'm aware of JSR303 but have not worked with it and don't quite understand how to apply it in a Spring context.
From what I understand, another option would be to bind the #RequestBody to an entire domain object and add validation constraints there, but currently my code is set up to accept individual parameters as shown above.
What is the most straightforward way to apply validation to input parameters using this approach?
This seems to be possible now (tried with Spring 4.1.2), see https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
Extract from above page:
Add MethodValidationPostProcessor to Spring #Configuration class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Add #Validated to controller class
Use #Size just before #RequestParam
#RequestMapping("/hi")
public String sayHi(#Size(max = 10, message = "name should at most 10 characters long") #RequestParam("name") String name) {
return "Hi " + name;
}
Handle ConstraintViolationException in an #ExceptionHandler method
There's nothing built in to do that, not yet anyway. With the current release versions you will still need to use the WebDataBinder to bind your parameters onto an object if you want automagic validation. It's worth learning to do if you're using SpringMVC, even if it's not your first choice for this task.
It looks something like this:
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#Valid #ModelAttribute() FooWrapper fooWrapper,
BindingResult errors) {
if (errors.hasErrors() {
//handle errors, can just return if using Spring form:error tags.
}
}
public static class FooWrapper {
#NotNull
#Size(max=32)
private String fooName;
private String description;
//getset
}
If you have Hibernate Validator 4 or later on your classpath and use the default dispatcher setup it should "Just work."
Editing since the comments were getting kind of large:
Any Object that's in your method signature that's not one of the 'expected' ones Spring knows how to inject, such as HttpRequest, ModelMap, etc, will get data bound. This is accomplished for simple cases just by matching the request param names against bean property names and calling setters. The #ModelAttribute there is just a personal style thing, in this case it isn't doing anything. The JSR-303 integration with the #Valid on a method parameter wires in through the WebDataBinder. If you use #RequestBody, you're using an object marshaller based on the content type spring determines for the request body (usually just from the http header.) The dispatcher servlet (AnnotationMethodHandlerAdapter really) doesn't have a way to 'flip the validation switch' for any arbitrary marshaller. It just passes the web request content along to the message converter and gets back a Object. No BindingResult object is generated, so there's nowhere to set the Errors anyway.
You can still just inject your validator into the controller and run it on the object you get, it just doesn't have the magic integration with the #Valid on the request parameter populating the BindingResult for you.
If you have multiple request parameters that need to be validated (with Http GET or POST). You might as well create a custom model class and use #Valid along with #ModelAttribute to validate the parameters. This way you can use Hibernate Validator or javax.validator api to validate the params. It goes something like this:
Request Method:
#RequestMapping(value="/doSomething", method=RequestMethod.GET)
public Model dosomething(#Valid #ModelAttribute ModelRequest modelRequest, BindingResult result, Model model) {
if (result.hasErrors()) {
throw new SomeException("invalid request params");
}
//to access the request params
modelRequest.getFirstParam();
modelRequest.getSecondParam();
...
}
ModelRequest class:
class ModelRequest {
#NotNull
private String firstParam;
#Size(min = 1, max = 10, message = "You messed up!")
private String secondParam;
//Setters and getters
public void setFirstParam (String firstParam) {
this.firstParam = firstParam;
}
public String getFirstParam() {
return firstParam;
}
...
}
Hope that helps.
I need to show custom messages in my Spring 3.0 application. I have a database with Hibernate and there are several constraints. I have doubts in how DataIntegrityViolationException should be handled in a good way. I wonder if there is a way to map the exception with a message set in a properties file, as it is possible in Constraints validation. Could I handle it automatically in any way or I have to catch this exception in each controller?
The problem with showing user-friendly messages in the case of constraint violation is that the constraint name is lost when Hibernate's ConstraintViolationException is being translated into Spring's DataIntegrityViolationException.
However, you can customize this translation logic. If you use LocalSessionFactoryBean to access Hibernate, you can supply it with a custom SQLExceptionTranslator (see LocalSessionFactoryBean.jdbcExceptionTranslator). This exception translator can translate a ConstraintViolationException into your own exception class, preserving the constraint name.
I treat DataIntegrityViolationException in ExceptionInfoHandler, finding DB constraints occurrences in root cause message and convert it into i18n message via constraintCodeMap:
#RestControllerAdvice
public class ExceptionInfoHandler {
#Autowired
private final MessageSourceAccessor messageSourceAccessor;
private static Map<String, String> CONSTRAINS_I18N_MAP = Map.of(
"users_unique_email_idx", EXCEPTION_DUPLICATE_EMAIL,
"meals_unique_user_datetime_idx", EXCEPTION_DUPLICATE_DATETIME);
#ResponseStatus(value = HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) {
String rootMsg = ValidationUtil.getRootCause(e).getMessage();
if (rootMsg != null) {
String lowerCaseMsg = rootMsg.toLowerCase();
for (Map.Entry<String, String> entry : CONSTRAINS_I18N_MAP.entrySet()) {
if (lowerCaseMsg.contains(entry.getKey())) {
return logAndGetErrorInfo(req, e, VALIDATION_ERROR, messageSourceAccessor.getMessage(entry.getValue()));
}
}
}
return logAndGetErrorInfo(req, e, DATA_ERROR);
}
...
}
Can be simulated in my Java Enterprise training application by adding/editing user with duplicate mail or meal with duplicate dateTime.
UPDATE:
Other solution: use Controller Based Exception Handling:
#RestController
#RequestMapping("/ajax/admin/users")
public class AdminAjaxController {
#ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<ErrorInfo> duplicateEmailException(HttpServletRequest req, DataIntegrityViolationException e) {
return exceptionInfoHandler.getErrorInfoResponseEntity(req, e, EXCEPTION_DUPLICATE_EMAIL, HttpStatus.CONFLICT);
}
Spring 3 provides two ways of handling this - HandlerExceptionResolver in your beans.xml, or #ExceptionHandler in your controller. They both do the same thing - they turn the exception into a view to render.
Both are documented here.
1. In your request body class check for not null or not empty like this
public class CustomerRegisterRequestDto {
#NotEmpty(message = "first name is empty")
#NotNull(message = Constants.EntityValidators.FIRST_NAME_NULL)
private String first_name;
//other fields
//getters and setters
}
2. Then in your service check for this
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<CustomerRegisterRequestDto>> violations = validator.validate(userDto);
if (!violations.isEmpty()) {
//something is wrong in request parameters
List<String> details = new ArrayList<>();
for (ConstraintViolation<CustomerRegisterRequestDto> violation : violations) {
details.add(violation.getMessage());
}
ErrorResponse error = new ErrorResponse(Constants.ErrorResponse.REQUEST_PARAM_ERROR, details);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
3. Here is your ErrorResponse class