JSR - 349 bean validation for Spring #RestController with Spring Boot - java

I am using Spring Boot 1.5.2.RELEASE and not able to incorporate JSR - 349 ( bean validation 1.1 ) for #RequestParam & #PathVariable at method itself.
For POST requests, if method parameter is a Java POJO then annotating that parameter with #Valid is working fine but annotating #RequestParam & #PathVariable with something like #NotEmpty, #Email not working.
I have annotated controller class with Spring's #Validated
There are lots of questions on SO and I have commented on this answer that its not working for me.
Spring Boot includes - validation-api-1.1.0.Final.jar and hibernate-validator-5.3.4.Final.jar .
Am I missing anything?
Example code ,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(#Email #NotEmpty #RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
response.setResponse(Constants.SUCCESS);
response.setMessage("testValidated");
logger.error("Validator Not called");
return response;
}
Below handler is never called when I send empty values or not well formed email address for email & control always goes to with in testValidated method.
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseBean handle(ConstraintViolationException exception) {
StringBuilder messages = new StringBuilder();
ResponseBean response = new ResponseBean();
exception.getConstraintViolations().forEach(entry -> messages.append(entry.getMessage() + "\n"));
response.setResponse(Constants.FAILURE);
response.setErrorcode(Constants.ERROR_CODE_BAD_REQUEST);
response.setMessage(messages.toString());
return response;
}
ResponseBean<T> is my application specific class.

I had asked the question after more than two days of unsuccessful hit & trial. Lots of confusing answers are out there because of confusions around Spring Validations and JSR validations, how Spring invokes JSR validators, changes in JSR standards & types of validations supported.
Finally, this article helped a lot.
I solved problem in two steps,
1.Added following beans to my Configuration - without these beans , nothing works.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor mvProcessor = new MethodValidationPostProcessor();
mvProcessor.setValidator(validator());
return mvProcessor;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
return validator;
}
2.Placed Spring's #Validated annotation on my controller like below,
#RestController
#RequestMapping("/...")
#Validated
public class MyRestController {
}
Validated is - org.springframework.validation.annotation.Validated
This set up doesn't affected #Valid annotations for #RequestBody validations in same controller and those continued to work as those were.
So now, I can trigger validations like below for methods in MyRestController class,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated" , consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(
#Email(message="email RequestParam is not a valid email address")
#NotEmpty(message="email RequestParam is empty")
#RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
....
return response;
}
I had to add another handler in exception handler for exception - ConstraintViolationException though since #Validated throws this exception while #Valid throws MethodArgumentNotValidException

Spring #Validated #Controller did not mapped when adding #Validated. Removal of any inheritance from controller itself did help. Otherwise Sabir Khan's answer worked and did help.

Related

Spring 5 PathVariable Validation Causing Http 404

When I add #Validated to a Spring RestConstroller I am receiving a HTTP 404 response for that Controllers endpoints. Before adding the annotation the endpoint is found without any issues.
The issue is with #PathVariable specifically and I have validation working correctly (using #Valid) with non-primitive RequestBody parameters.
There are quite a few articles on this and I've spent a significant amount of time trying several variations without success. The most simple is .
According to this simple example https://sdqali.in/blog/2015/12/05/validating-requestparams-and-pathvariables-in-spring-mvc/ I believe it should be as simple as adding #Validated to the Controller, leaving off #Valid for the PathVariable parameter and adding a MethodValidationPostProcessor bean. Preferably I would use #Valid instead of #Validated.
I am using Spring 5 (not boot) and have hibernate-validator 6.0.16.Final on the classpath
Problematic RestController
#Validated
#RestController
#RequestMapping(value = "practices")
public class RestPracticeResource implements PracticeResource {
#Override
#GetMapping("/{id}")
public ResponseEntity<Practice> getPractice(#Id #PathVariable("id") final String id) throws ResourceNotFoundException {
final UUID uuidId = UUID.fromString(id);
}
}
Working RestController for RequestBody
#RestController
#RequestMapping(value = "accounts")
public class RestAccountResource implements AccountResource {
#Override
#PostMapping
public ResponseEntity<AccountDto> create(#NotNull #Valid #RequestBody final CreateAccountDto createAccountDto)
throws ResourceAlreadyExistsException {
...
}
}
Id Annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = Id.IdValidator.class)
public #interface Id {
// ... Stock standard code here
class IdValidator implements ConstraintValidator<Id, String> {
#Override
public void initialize(final Id id) {}
#Override
public boolean isValid(final String id, final ConstraintValidatorContext constraintValidatorContext) {
// Basic REGEX match for UUID format
return ValidationUtil.isValidId(id);
}
}
}
ExceptionHander
#ExceptionHandler(ConstraintViolationException.class)
public final ResponseEntity<ApiError> handleConstraintViolationException(final ConstraintViolationException ex,
final WebRequest webRequest) {
// .. extract violations into standard format
return new ResponseEntity<>(apiError, BAD_REQUEST);
}
MethodValidationPostProcessor bean
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Actual Results
As soon as I add #Validated I receive Http 404 responses for the /practices/{id} endpoint not matter what format id I provider. If I remove #Validated I can pass a valid UUID and all is ok or pass an invalid UUID and get an Exception thrown from UUID.fromString(id).
#Id is ignored if I attempt to use #Valid as per the request body validation.
Expected Results
Adding #Validated will enabled the #Id annotation validation and a ConstraintViolationException is thrown when a non-UUID formatted {id} is provided.
Here's an old question that is similar to this and i found the solution i was looking for. Link: Spring rest controller #RequestParam validation
To summarize Szymon Stepniak's answer. This is a spring-mvc bug. I'm using hibernate version 5.2.4.Final and spring-webmvc 4.3.8.RELEASE version.
If the rest controller implements an interface and we also add the #Validated annotation the RequestMappingHandlerMapping will not register the endpoint. I am not aware of the root cause. But removing the "implement interface" solved the problem for me.
Use the Proxy Target Class and it will work with
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
I added the usual stuff you have done (libraries in pom, #Valid on RequestBody etc) and got a 404 too!
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b11</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
What the Spring docs (and many blogs) leave it as subtle is that Spring looks for an exit point to throw the error but if that exit doesn't exist, it will reply with 404. After reading a lot especially this blog, adding this class got Spring to recognize #Valid and find an exit point to throw the error
#RestControllerAdvice
#Order(1)
public class RestControllerExceptionHandler {
#RequestMapping(value = { "error/404" }, method = RequestMethod.GET)
#ExceptionHandler(Exception.class)
public String handleUnexpectedException(Exception e) {
return "views/base/rest-error";
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handlemethodArgumentNotValid(MethodArgumentNotValidException exception) { //
// TODO you can choose to return your custom object here, which will then get transformed to json/xml etc.
return "Sorry, that was not quite right: " + exception.getMessage();
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
public String handleConstraintViolation(ConstraintViolationException exception) { //
// TODO you can choose to return your custom object here, which will then get transformed to json/xml etc.
return "Sorry, that was not quite right: " + exception.getMessage();
}
}

how can i add validation to a spring boot rest endpoint with a string parameter?

how to validate a spring boot rest endpoint with a String parameter, here is the sample of my endpoint.
#ResponseBody
#RequestMapping(value = "/getProfile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getProfile(#RequestBody #Valid #NotEmpty String profileId){}
even thought i added #valid and #NonEmpty constraint its not getting validated.
but its working for a other classes. for eg,
#ResponseBody
#RequestMapping(value = "/saveProfiles", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> saveProfiles(#RequestBody #Valid PersistProfilesRequest request){}
here all the fields in PersistProfilesRequest class which have a constrain applied is validating properly.
validation is not happening for method parameter with java class like String and Map.
i am using spring boot 1.3.0 with hibernate validator.
how can i add validation to a rest endpoint with a string parameter?
EDIT
i am using junit and mockmvc to test the endpoint, below is the test case
#Test
public void testGetProfile() throws Exception{
String profileId = " ";
this.mockMvc.perform(post("/getProfile").content(profileId).accept(MediaType.APPLICATION_JSON).contentType("application/json"))
.andDo(print())
.andExpect(status().isInternalServerError());
}
You can try to annotate your controller class with #Validated. In my case it helped.
#Validated
#RestController
#RequestMapping(path = "/api/somethin")
public class LegacyRestController {

Spring MVC: controller method params w/o #RequestParam

Recently I've faced a strange behavior in Spring MVC (v4.3.7.RELEASE), especially from the RequestParamMethodArgumentResolver. What I tried to do is getting a link to my controller using MvcUriComponentsBuilder, so here are 2 examples:
What's clear
Having a controller like this
#Controller
#RequestMapping("/test")
public class TestController {
#GetMapping("/get")
#ResponseBody
public ModelAndView get(#RequestParam(required = false) String param1,
#RequestParam(required = false) String param2) {
...
}
}
And then using MvcUriComponentsBuilder as fromMethodCall(on(TestController.class).get(null, null)).toUriString() results in expected link to /test/get.
What's not clear
Same controller, but w/o #RequestParam annotations; same usage of MvcUriComponentsBuilder. But the result is /test/get?param1&param2, i.e. it behaves as if they were annotated with #RequestParam(required = true)...
So before creating a bug for Spring just wanted to clarify if I'm not mistaken anywhere: should the absence of #RequestParam really behave the same as #RequestParam(required = false)? I was not able to find it in documentation, but it's in the code actually (see https://github.com/spring-projects/spring-framework/blob/v4.3.7.RELEASE/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java#L79)

Is it possible to validate #RequestParam in Spring MVC REST endpoint?

In Jersey 2 it is possible to do this:
#GET
#PATH("user/{email}")
public IDto getUser(#NotNull #Email #PathParam("email") String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
But I cannot make something similar to work in Spring MVC, it seems that the validation is only done when providing an object in #RequestBody or using an SpringMVC Form, for example the following won't work:
#RequestMapping(value="/user/{email}", method = RequestMethod.GET)
public #ResponseBody IDto getUser(#NotNull #Email #PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
There are other similar questions, but those seem to be oriented to Spring MVC UI applications, in my case it is only a REST API which returns JSON response so I don't have any View to map/bind to the controller.
Seems it is possible, using #Validated.
Here's an example.
Based on OP's question, this should work:
#RestController
#Validated
public class MyController {
#GetMapping(value="/user/{email}")
public #ResponseBody IDto getUser(#NotNull #Email #PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
}
In plain Spring implementations, it may be required to manually register the validator bean:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
1- Simply add #Validated annotation at the top of your class.
2- Put whatever annotations for validations (#NotBlank, Min(1), etc.) before the #RequestParam annotation in your method signature.
The validated annotation from the org.springframework.validation.annotation.Validated package to validate a #PathVariable. Make sure the class annotated with #Validated.
#GetMapping("/name-for-day/{dayOfWeek}")
public String getNameOfDay(#PathVariable("dayOfWeek") #Min(1) #Max(7) Integer dayOfWeek) {
return dayOfWeek + "";
}
As far as I can tell, you cannot do this out-of-the-box with Spring.
Options:
Use a regular expression:
#RequestMapping(value="/user/{email:SOME_REXEXP}", method = RequestMethod.GET)
public #ResponseBody IDto getUser(#PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
Use Hibernate Validator to validate the method. Either call the validator manually, or make Spring call it for you using AOP. See https://github.com/gunnarmorling/methodvalidation-integration
Controller should be annotated with spring's #Validated
So update your code with
#Validated
#RequestMapping(value="/user/{email}", method = RequestMethod.GET)
public #ResponseBody IDto getUser(
#NotNull
#Email
#PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}

How to create a default method in SpringMVC using annotations?

I can't find a solution to this, and it's driving me crazy. I have #Controller mapped that responds to several methods using #RequestMapping. I'd like to tag one of those methods as default when nothing more specific is specified. For example:
#Controller
#RequestMapping("/user/*")
public class UserController {
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
So /user/login -> login method, /user/logout -> logout, etc. I'd like to make it so that if someone goes to /user then it routes to one of these methods. However, I don't see anything on #RequestMapping that would allow me to specify one of these methods as a default handler. I also don't see any other annotations that might be used on the class either to do this. I'm beginning to suspect it doesn't exist.
I'm using Spring 2.5.6. Is this solved in 3.0.0? I might just hack Spring to make it work because it's tremendously annoying this isn't more straightforward.
Thanks in Advance.
Take a look at this answer:
Spring MVC and annotated controllers issue
What if you annotate a method with:
#RequestMapping(method = RequestMethod.GET)
You can see an example here:
Spring 3.0 MVC + Hibernate : Simplified with Annotations – Tutorial
The same behavior can be seen here:
Spring Framework 3.0 MVC by Aaron Schram (look at page 21)
Short answer: I do not know how to simply specify one method as default with a simple tag.
But there is this ...
I do not know in which version of Spring this was implemented, but you can provide multiple values to #RequestMapping in 3.1.2. So I do this:
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(value = {"", "/", "/list"}, method = RequestMethod.GET)
public String listUsers(ModelMap model) { }
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ModelAndView add(HttpServletRequest request, ModelMap model) { }
}
The following URLs then map to listUsers():
http://example.com/user
http://example.com/user/
http://example.com/user/list
I would create one default method without RequestMapping's value in there. Please see method defaultCall() below. You can then simply call them with URL: [yourhostname]/user
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String defaultCall( MapModel model ) {
//Call the default method directly, or use the 'forward' String. Example:
return authenticate( model );
}
#RequestMapping("login")
public String login( MapModel model ) {}
#RequestMapping("logout")
public String logout( MapModel model ) {}
#RequestMapping("authenticate")
public String authenticate( MapModel model ) {}
}
Ref: Spring Framework Request Mapping
Simply using #RequestMapping("**") on your default method should work. Any more specific mappings should still pick up their requests. I use this method for setting up default methods sometimes. Currently using Spring v4.3.8.RELEASE.

Categories

Resources