Springboot custom validation jsr 303 is not working - java

I was trying to add custom annotation validation on the age field. as I saw some JSR 303 topics
Controller
#Validated
#Controller
public class StudentController {
#Autowired
private StudentRepository repository;
#PostMapping("/send")
public ResponseEntity saveStudent(#AgeValidator #RequestBody Student student) {
System.out.println("saveStudent invoked");
repository.save(student);
ResponseData responseData = new ResponseData();
responseData.setResultId("result");
responseData.setResultValue("saved");
ResponseEntity entity = new ResponseEntity(responseData, HttpStatus.OK);
return entity;
}
}
Model
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#Column(length = 7)
private String Id;
private String fullName;
private Integer age;
private String department;
}
AgeValidator
package com.example.demo4;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = AgeValidatorImpl.class)
public #interface AgeValidator {
String message()
default "Please enter a valid age";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
AgeValidatorImpl
class AgeValidatorImpl implements ConstraintValidator<AgeValidator, Student> {
#Override
public void initialize(AgeValidator constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation); //To change body of generated methods, choose Tools | Templates.
}
#Override
public boolean isValid(Student t, ConstraintValidatorContext cvc) {
System.out.println("AgeValidatorImpl invoked");
if (t.getAge() < 18) {
return false;
} else if (t.getAge() > 40) {
return false;
}
return true;
}
}
so if am sending using postman at any age it saves the record and it's not validating. I saw many peoples commented to add annotation on controller #validated which I import from import org.springframework.validation.annotation.Validated. Still Why this is not working. what am missing?

You can try this,
I have updated #Age annotation. You can provide upper and lower limit for validation. Note I that have added ElementType.FIELD to #Target. It allows you to use this in class fields as well.
#Documented
#Target({ElementType.PARAMETER, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = AgeValidator.class)
public #interface Age {
int lower() default 14;
int upper() default 60;
String message() default "Please enter a valid age";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
This is the validation constrain for the annotation.
public class AgeValidator implements ConstraintValidator<Age, Integer> {
private int upperLimit;
private int lowerLimit;
#Override
public boolean isValid(Integer i, ConstraintValidatorContext constraintValidatorContext) {
return lowerLimit < i && i < upperLimit;
}
#Override
public void initialize(Age constraintAnnotation) {
this.lowerLimit = constraintAnnotation.lower();
this.upperLimit = constraintAnnotation.upper();
}
}
You can pass the annotation to class fields and override the upper and lower limit.
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#Column(length = 7)
private String Id;
private String fullName;
#Age(lower = 10, upper = 70)
private Integer age;
private String department;
}
Used #Validated annotation to validate the Student object against all the validation constraints.
#PostMapping("/send")
public ResponseEntity saveStudent(#Validated #RequestBody Student student)
Update
Replace this dependency,
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
<type>jar</type>
</dependency>
by this
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Related

Custom Datetime validation not working in Rest

I have created a custom date Validator for checking the timestamp string like as shown below. The code is working fine, but validation is not working and no message is showing in the response body.
When I post as data like as shown below
{
"price": "0",
"timestamp": "foo foo"
}
it is giving me 200. My exception is to get the valid exception details. Can anyone please help me on this
StockController.java
#RestController
#AllArgsConstructor
#RequestMapping(value = "/stock", produces = MediaType.APPLICATION_JSON_VALUE)
public class StockController {
#Autowired
private StockService stockService;
#PostMapping
public void createStock(#Valid #RequestBody final Stock stock) {
stockService.create(stock);
}
}
DefaultControllerAdvice.java
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
#ControllerAdvice
public class DefaultControllerAdvice {
#ExceptionHandler(Exception.class)
#ResponseBody
public Exception handleException(Exception exception){
return exception;
}
}
Stock.java
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import com.challenge.validators.TimestampValidator;
import lombok.Data;
#Data
public class Stock {
#Min(0)
#NotNull
public double price;
#NotNull(message="Timestamp cannot be empty")
#DateTimeValidator
public String timestamp;
}
DateTimeValidator.java
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = DateTimeValidatorCheck.class)
#Documented
public #interface DateTimeValidator {
String message() default "Must be timestamp of format YYYY-MM-DDThh:mm:ss.sssZ";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
DateTimeValidatorCheck.java
public class DateTimeValidatorCheck implements ConstraintValidator<DateTimeValidator, String> {
#Override
public void initialize(DateTimeValidator dateTimeValidator ) {
}
#Override
public boolean isValid(String timestamp, ConstraintValidatorContext context) {
if (timestamp == null) {
return false;
} else {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
try {
LocalDate.parse(timestamp, dateTimeFormatter);
return true;
} catch (DateTimeParseException dateTimeParseException) {
return false;
}
}
}
}
You need a BindingResult bindingResult parameter on your createStock method. Use this to check for validation errors:
if (bindingResult.hasErrors()) {
// return error or throw exception
}

Custom UniqueUsername validation with Spring

I want to assure that the username is unique when creating a new one or updating one username. I wrote the code below and it works fine for creating and updating the username. But when updating only other user fields, like age or sex, and maintaining the same username, it returns invalid because the username already exists.
It's important to say that the validation I want is using BindingResult. I already have a database which considers username a unique constraint. But now I want the same validation with BindingResult. But the way I did causes error when updating an user.
Defining of the annotation #UniqueUsername
package app.condominio.validator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = UniqueUsernameValidator.class)
#Target({ ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueUsername {
String message() default "Nome de usuário já está sendo usado";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Implementing the validator
package app.condominio.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import app.condominio.service.UsuarioService;
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {
#Autowired
UsuarioService usuarioService;
#Override
public void initialize(UniqueUsername username) {
}
#Override
public boolean isValid(String username, ConstraintValidatorContext cxt) {
return !usuarioService.existe(username);
}
}
In Usuario.class
#Entity
#Table(name = "Usuarios")
public class Usuario implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#UniqueUsername
private String username;
#NotBlank
private String password;
#NotBlank
private String nome;
#Email
private String email;
...
UsuarioService
#Service
#Transactional
public class UsuarioServiceImpl implements UsuarioService {
#Autowired
private UsuarioDao usuarioDao;
#Override
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public boolean existe(String username) {
return usuarioDao.existsByUsername(username);
}
UsuarioDao
public interface UsuarioDao extends CrudRepository<Usuario, Long> {
Usuario findByUsername(String username);
Boolean existsByUsername(String username);
}
Controller
#Controller
#RequestMapping("conta")
public class UsuarioController {
#Autowired
private UsuarioService usuarioService;
#GetMapping("/cadastrar/sindico")
public ModelAndView preCadastro(#ModelAttribute("usuario") Usuario usuario, ModelMap model) {
model.addAttribute("conteudo", "cadastrarSindico");
return new ModelAndView("site/layout", model);
}
#PostMapping("/cadastrar/sindico")
public ModelAndView posCadastro(#Valid #ModelAttribute("usuario") Usuario usuario, BindingResult validacao, ModelMap model) {
if (validacao.hasErrors()) {
return preCadastro(usuario, model);
}
usuarioService.salvarSindico(usuario);
model.addAttribute(usuario);
model.addAttribute("conteudo", "cadastrarCondominio");
return new ModelAndView("site/layout", model);
}
I think UniqueUsernameValidator actions as expected.
I am not very clear about your application.So it is difficult to implement an method to to check if validating user have the same id or not of user in database.May be you should provide more details, such as code of method usuarioService.existe(username).
For me, to assure that the username is unique, i do this:
#Column(unique=true)
String username;
or use #UniqueConstraint
#Table(name = "users", uniqueConstraints = {
#UniqueConstraint(columnNames = {
"username"
})
})
but note that it will work if you let JPA create your tables.
#UniqueConstraint
You can try following approach using validation groups:
Create a marker interface (or class)
public interface UserCreateValidationGroup {
}
Then use it in you entity like following
#NotBlank
#UniqueUsername(groups = UserCreateValidationGroup.class)
private String username;
And then use it in Controller along with #Validated (Spring's variant for JSR-303). Note that you'll have to split your single method into separate create and update methods, to distinct validation.
Create:
#PostMapping("/cadastrar/sindico")
public ModelAndView create(
#Validated(UserCreateValidationGroup.class) #ModelAttribute("usuario") Usuario usuario,
BindingResult validacao,
ModelMap model
) { ... }
Update:
// note, here I did use PUT and no argument for #Validated annotation
#PutMapping("/cadastrar/sindico")
public ModelAndView update(
#Validated #ModelAttribute("usuario") Usuario usuario,
BindingResult validacao,
ModelMap model
) { ... }
However, you'll have to perform manual check (UsuarioService.existe) for username in the update case.

Validating member List variables for null values

The structure that I have is something like below:
Class A{
String str;
int i;
List<B> bs;
C c;
#NotNull
List<D> ds;
}
Class B{
#NotNull
List<E> es;
}
Class C{
List<String> s;
}
Class E{
#NotNull
List<String> s;
}
For the list variables that are annotated with #NotNull I need to throw validation error if any of them variables has one or more null objects. While for the other list variables I just need to remove the nulls;
What would be the best way to achieve this?
If you are using validation 2.0+ you can put annotation inside: List<#NotNull String> s;
You should define custom annotation for validating.
so define custom annotation like bellow.
#Target({ElementType.FIELD, ElementType.PARAMETER,ElementType.ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = ValidateListValidator.class)
#Documented
public #interface ValidateList {
}
and implement ValidateListValidator like this:
public class ValidateListValidator implements ConstraintValidator<ValidateList, List<Object>> {
private ValidateList validateList;
#Override
public void initialize(ValidateList validateList) {
this.validateList = validateList;
}
#Override
public boolean isValid( List<Object> list, ConstraintValidatorContext constraintValidatorContext) {
return list.stream().noneMatch(Objects::isNull);
}
}
and for test it
#Test
public void test() {
boolean valid = validator.isValid(Arrays.asList("test","this",null),context);
assertThat(valid, is(false));
}
This is the final code that I wrote, just a few tweaks to the code that Hadi posted. I hope it helps:
Annotation:
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
#Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ListValidator.class)
#Documented
public #interface ValidList {
String message() default "Null values are not allowed in array fields.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator Class:
import java.util.List;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ListValidator implements ConstraintValidator<ValidList, List<? extends Object>> {
#Override
public boolean isValid(List<? extends Object> list, ConstraintValidatorContext context) {
return list.stream().noneMatch(Objects::isNull);
}
#Override
public void initialize(ValidList constraintAnnotation) {}
}

Custom annotation not called

I have made custom annotation for validation of my bean field. I use #Age(value = 10) annotation for validation age. I have write code as below.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = AgeConstraintValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Age {
String message() default "{Age is not valid }";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int value();
}
This is code for age constrain validator
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class AgeConstraintValidator implements ConstraintValidator< Age, Long> {
private Age age;
#Override
public void initialize(Age age) {
this.age = age;
}
#Override
public boolean isValid(Long dob, ConstraintValidatorContext context) {
System.out.print(" age in annotion");
if(dob != age.value()){
return true;
}
return false;
}
}
Now when i use #Age( value = 10) in my bean so it is not called Age annotation. Can anyone tell me any fault in my code. When i create my bean object and assign age differ for test but i can not get any validation on bean 's field .
Spring will not take this custom annotation automatically. You have to let Spring know about by defining a BeanPostProcessor. create a class which implements it
For e-g
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}
and mention about this bean in your spring config xml as below
<bean class="<your pack>.InitHelloWorld" />

How to validate 2 field with OR condition?

I want to validate two fields of a Request Class in manner that Either one field is valid OR another field is valid.
Eg:
Request Bean
public class CarRequest {
#NotEmpty
private String customerName;
#NotEmpty
private String customerId;
Controller Method
public #ResponseBody CarResponse addCar(
#ModelAttribute #Valid CarRequest request, BindingResult results)
throws RuntimeException, ValidationException {
if (results.hasErrors()) {
LOG.error("error occured while adding the car");
throw new ValidationException(
"Error Occoured while validiating car request");
}
}
Here I want to check that either customerName should be NotEmpty OR customerId should be NotEmpty. then my validation should pass. How can I implement it . Please suggest!!
You need to create custom validator to validate multiple fields.
create a custom annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = CarRequestValidator.class)
#Target({ ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestAnnotation {
String message() default "{RequestAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
create a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CarRequestValidator implements
ConstraintValidator<RequestAnnotation, CarRequest> {
#Override
public void initialize(RequestAnnotation constraintAnnotation) {
}
#Override
public boolean isValid(CarRequest value, ConstraintValidatorContext context) {
// validation logic goes here
return false;
}
}
Now, annotate your model with custom annotation:
#RequestAnnotation
public class CarRequest {
private String customerName;
private String customerId;
}

Categories