JSR 380 specification for constraints for Java Beans, Validation via API or automatically (JPA, JSF-Spring MVC-GWT, JAX-RS), extensible which constraints, BV 1.1 method validation.
It’s also named as Jakarta Bean Validation and we can implements with certified implementation Hibernate Validation. Here is Jakarta Bean Validation v2.0 specification.
Hibernate reference guide site.
Maven dependencies:
spring-boot-starter-web starter dependency contains Bean Validation for Spring Boot Web Application.
org.springframework.boot_spring-boot-starter-validation dependency contains Bean Validation for Spring Boot Standalone Application.
If you are not working with Spring Boot application, you need the javax.validation_validation-api and org.hibernate_hibernate-validator dependencies.
First Way:
Bean Validation for object:
[code lang=”java”]
public class Person {
@NotEmpty(message = "Name can not be empty")
private String name;
@Email(message = "Not valid email")
private String email;
@NotNull(message = "Age can not be empty")
@Min(value = 18, message = "We only allow an adult.")
private int age;
}
[/code]
Bean Validation on Spring:
We can check with Request Body, Path variables, Query parameters for Spring MVC application or REST API.
Spring MVC Request Body validation:
[code lang=”java”]
@PostMapping("/student")
public String createStudent(@Valid Student student, BindingResult bindingResult, Model model){
if(bindingResult.hasErrors()){
return "student";
}
model.addAttribute("msg", "Student added");
model.addAttribute("student", student);
return "student";
}
[/code]
This code snippet will validate the Student data model, when a validation error occurred then system will be sent a validation error message in the response.
Java Beans validations for method, field, annotation type, constructor, parameter, type use:
@Size
@NotEmpty
@NotBlank
@Email
etc.
*
*
Cascade validation:
@Valid
etc.
*
*
Optional<@Email String> getEmail() {…};
*
*
Custom containers:
Specific collection types – Google Guava
JVM languages – Ceylon, Scala
*
*
@Positive
@PositiveOrZero
@Negative
@NegativeOrZero
private Table revenuePerYearAndCategory;
*
*
Date restriction:
@Future
@Past
@PastOrPresent
@FutureOrPresent
@Future
private LocalDate deliveryDate = LocalDate.of(2020, Month.MARCH, 09);
*
*
NotNullValidator.isValid
You can also read the others Spring MVC Validation posts:
javadevjournal.com spring-boot/spring-custom-validation-message-source/
Second way:
For Spring Boot Rest Controller:
[code lang=”java”]
@PostMapping("/student/add")
public ResponseEntity<?> createStudent(@Valid @RequestBody Student student) {
ModelResponse modelResponse = studentService.create(student);
return new ResponseEntity<>(modelResponse, HttpStatus.OK);
}
[/code]
[code lang=”java”]
@ControllerAdvice
public class RestApiExceptionHandler {
private final MessageSource msgSrc;
public RestApiExceptionHandler(MessageSource msgSrc) {
this.msgSrc = msgSrc;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
// for internationalization
// errorMessage = msgSrc.getMessage(errorMessage, LocaleContextHolder.getLocale())
errors.put(fieldName, errorMessage);
});
return errors;
}
}
[/code]
You can also refer to these posts.
baeldung.com spring-boot-bean-validation
For Custom Validation MessageSource:
baeldung.com spring-custom-validation-message-source
javadevjournal.com spring-boot/spring-custom-validation-message-source/
Third way:
If we don’t want to use domain annotations then we can do validation like this:
[code lang=”java”]
@RestController
@RequestMapping(value = "/auth")
public class AuthenticationRestApi {
@Autowired
private LogInReqValidation logInReqValidation;
@RequestMapping(value = "/logIn", method = RequestMethod.POST)
public ResponseEntity<?> logIn(@RequestBody LogInReq logInReq, BindingResult errors) throws Exception {
logInReq.setOperation(LogInOpType.LOGIN.getValue());
logInReqValidation.validateLogIn(logInReq, errors);
if (errors.hasErrors()) {
List<String> errMsgList = errors.getAllErrors()
.stream()
.map(o -> o)
.collect(Collectors.toList());
throw new IllegalArgumentException(MyStringUtils.listStringToString(errMsgList));
}
LogInResp logInResp = authenticationService.logInOp(logInReq);
return new ResponseEntity<>(logInResp, HttpStatus.OK);
}
}
[/code]
[code lang=”java”]
@Component
public class LogInReqValidation {
public void validateLogIn(LogInReq logInReq, BindingResult errors) {
if (Objects.nonNull(logInReq)
&& Arrays.stream(LogInOpType.values()).anyMatch((t) -> t.getValue() == logInReq.getOp())) {
if ((LogInOpType.LOGIN.getValue() == logInReq.getOp() && logInReq.hasEmptyLogInForbiddenFields())) {
errors.reject(HttpStatus.BAD_REQUEST.getReasonPhrase(), "Username and password can not be null");
}
} else {
errors.reject("404", "Login object can not be null");
}
}
}
[/code]
[code lang=”java”]
@Data
@NoArgsConstructor
public class LogInReq {
private String userName;
private String pwdHash;
private String oldPwdHash;
private int operation;
public boolean hasEmptyLogInForbiddenFields() {
if (MyStringUtils.isEmpty(userName)
|| !Arrays.stream(LogInOpType.values()).anyMatch((t) -> t.getValue() == getOp())
|| MyStringUtils.isEmpty(pwdHash)) {
return true;
}
return false;
}
}
[/code]
You can also read these posts:
injavawetrust.com spring-mvc-12-form-validation-01/
If you can use internationalization you can change code like this:
[code lang=”java”]
if (errors.hasErrors()) {
List<String> errMsgList = errors.getAllErrors()
.stream()
.map(o -> msgSrc.getMessage(o, LocaleContextHolder.getLocale()))
.collect(Collectors.toList());
throw new IllegalArgumentException(MyStringUtils.listStringToString(errMsgList));
}
[/code]
[code lang=”java”]
@Component
public class LogInReqValidation {
MessageSource msgSrc;
public LogInReqValidation(MessageSource msgSrc) {
this.msgSrc = msgSrc;
}
public void validateLogIn(LogInReq logInReq, BindingResult errors) {
if (Objects.nonNull(logInReq)
&& Arrays.stream(LogInOpType.values()).anyMatch((t) -> t.getValue() == logInReq.getOp())) {
if ((LogInOpType.LOGIN.getValue() == logInReq.getOp() && logInReq.hasEmptyLogInForbiddenFields())) {
String i18nMsgKey = RuntimeErrCode.ERR_CANNOT_BE_NULL.name().replace(YcnStringUtils.UNDER_SCORE, YcnStringUtils.DOT);
String i18nUserNameKey = YcnStringUtils.convertToPropertyKey(FieldErrCode.FIELD_USERNAME);
String i18nUserName = msgSrc.getMessage(i18nUserNameKey, null, String.format("Couldn’t be find internationalization key %s", i18nMsgKey), LocaleContextHolder.getLocale());
String errMsg = msgSrc.getMessage(i18nMsgKey, new Object[] {i18nUserName}, String.format("Couldn’t be find internationalization key %s", i18nMsgKey), LocaleContextHolder.getLocale());
errors.reject(HttpStatus.BAD_REQUEST.getReasonPhrase(), errMsg);
}
} else {
errors.reject("404", "Login object can not be null");
}
}
}
[/code]
You can also refer to these posts:
dzone.com articles/exception-handling-and-i18n-on-spring-boots-apis-p
For custom error messages handling:
baeldung.com global-error-handler-in-a-spring-rest-api]]>