Spring boot HTTP GET custom validation - java

I have a controller's method with a getMapping and a pathValue:
#ResponseBody
#Validated
#GetMapping(produces = [MediaType.APPLICATION_JSON_UTF8_VALUE],
value = '/{person}')
PersonResponse getPersonInfo (
#Valid #PersonValidation
#PathVariable('person') String personId,
BindingResult bindingResult
) {
// Abort if received data is not valid
if (bindingResult.hasErrors()) {
throw new BadHttpRequest()
}
What I am trying to achieve is validating the uri's personId using my custom validation #PersonValidation but it is not working, the code is not giving any errors but the program never goes into my custom validation class...
¿How can I fix this?
¿How can I validate my path variable with a custom validation?

I have solved my problem. Spring have some problems to validate path variables...
First, you need to add a configuration class with a MethodValidationPostProcessor like this one:
/**
* this class is required to use our custom path variable validation on
* the controller class
*/
#Configuration
class ValidatorConfiguration {
#Bean
MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor()
}
}
Now, on the controller class, it is important to add #Validated annotation
#RestController
#Validated
class YourController class {
}
now your custom validation on the path variable is gonna work, if you want to catch the exception, add this method inside a exception handler class:
#ControllerAdvice
class GlobalExceptionHandler {
#ExceptionHandler(value = [ ConstraintViolationException.class ])
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
String handle(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations()
StringBuilder strBuilder = new StringBuilder()
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n")
}
return strBuilder.toString()
}
}

Related

How to validate java plain methods using javax validation

I am validating java beans using spring-boot-starter-validation.
Validation on the controller is working fine,
I want to know whether can we validate the normal methods of a class using #Valid annotation? I have tried it but not working.
My working solution on the controller
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#Valid #RequestBody InternalMsg internalMsg) {
return ResponseEntity.ok("Valid Message");
}
I want to move the validation to a class method so that when I hit the RestAPI, the validation errors are captured in a new method.
Let's say the method is validateMsg of class MsgValidator and I am calling this method inside controller code
#PostMapping("/testMessage")
ResponseEntity<String> testMethod(#RequestBody InternalMsg internalMsg) { // No #Valid here
MsgValidator msgValidator = new MsgValidator();
Boolean isValid = msgValidator.validateMsg(internalMsg);
// some other processings
return ResponseEntity.ok("Valid Message");
}
public class MsgValidator{
public boolean validateMsg(#Valid InteropMsg interopMsg){
return true;
}
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Ack> handleValidationExceptions(MethodArgumentNotValidException ex) {
StringBuilder errorMessages = new StringBuilder("");
ex.getBindingResult().getAllErrors().forEach((error) -> {
errorMessages.append(error.getDefaultMessage()).append(";");
});
log.error("Validation errors : "+errorMessages.toString());
return ResponseEntity.ok().body(ack);
}
}
public class InternalMsg implements Serializable {
#NotNull(message = "Msg Num is a required field")
private String msgNumber;
#NotNull(message = "Activity Name is a required field")
private String activityName;
}
This is not working
Please let me know how to achieve this
Below is an example of how you could use the ValidatorFactory to get a Validator to do the validation rather than using the #Valid annotation.
InternalMsg internalMsg = new InternalMsg();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<InternalMsg>> validate = validator.validate(internalMsg);
See here for more details -> https://www.baeldung.com/javax-validation
The below is just a snippet and not necessarily the recommended way of using the ValidationFactory

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

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

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.

"Invalid property 'ssn' of bean class [java.lang.String]" when trying to validate request body with Spring

I have a springboot application with a rest controller sitting up top. The user access the controller through /test and passes in a json like so:
{"ssn":"123456789"}
I want to validate the input by at least making sure there's not an empty ssn being passed in like so:
{"ssn":""}
So here's my controller:
#RequestMapping(
value = "/test",
method = RequestMethod.POST,
consumes = "application/json",
produces = "application/json")
#ResponseBody
public JsonNode getStuff(#RequestHeader HttpHeaders header,
#RequestBody String payload,
BindingResult bindingResult) {
validator.validate(payload, bindingResult);
if(bindingResult.hasErrors()) {
throw new InvalidRequestException("The request is incorrect", bindingResult);
}
/* doing other stuff */
}
And here's my validator:
#Component
public class RequestValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return false;
}
#Override
public void validate(Object target, Errors errors) {
ObjectMapper mapper = new ObjectMapper();
JsonNode ssn = null;
try {
ssn = mapper.readTree((String) target);
} catch (IOException e) {
e.printStackTrace();
}
if(ssn.path("ssn").isMissingNode() || ssn.path("ssn").asText().isEmpty()) {
errors.rejectValue("ssn", "Missing ssn", new Object[]{"'ssn'"}, "Must provide a valid ssn.");
}
}
}
I tried testing this with postman and I keep getting this error:
HTTP Status 500 - Invalid property 'ssn' of bean class [java.lang.String]: Bean property 'ssn' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
What exactly is the problem here? I don't understand what it's talking about in relation to getters and setters.
Edit 1: The value of the payload as requested
{"ssn":""}
By default Spring Boot configures Json parser, so any Json you pass to the controller will be parsed. Spring is expecting an object with a property called 'ssn' to bind the request value.
This means that you should create a model object like this:
public class Data {
String ssn;
}
And use it to bind your request body like this:
#RequestMapping(
value = "/test",
method = RequestMethod.POST,
consumes = "application/json",
produces = "application/json")
#ResponseBody
public JsonNode getStuff(#RequestHeader HttpHeaders header,
#RequestBody Data payload,
BindingResult bindingResult) {
validator.validate(payload, bindingResult);
if(bindingResult.hasErrors()) {
throw new InvalidRequestException("The request is incorrect", bindingResult);
}
/* doing other stuff */
}
You also need to adapt your Validator to use this new Data object.

How to handle DataIntegrityViolationException in Spring?

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

Categories

Resources