In this example, we will cover following things.
- Basic Validations
- Custom Defined Validation
- Reusing the Custom Validations for multiple classes
- Custom Validations with Group Sequence
- Response Object
- Control Advice Exception Class
Basic Validations like @NotEmpty , @Size , @NotNull etc.
@NotEmpty(message = "{account.name.notempty}",groups = {First.class})
@Size(min = 3,max = 50,message = "{account.name.size}",groups = {Second.class})
privateStringname;
@NotEmpty(message = "{account.type.notempty}",groups = {First.class})
@Size(min = 2,max = 50,message = "{account.type.size}",groups = {Second.class})
privateStringtype;
@NotNull(message = "{account.amount.notnull}",groups = {First.class})
@DecimalMin(value = "99.00",inclusive = false,message = "{account.amount.min}",groups = {Second.class})
@DecimalMax(value = "1000.00",inclusive = true,message = "{account.amount.max}",groups = {Second.class})
privateBigDecimalamount; Created one Custom Validator called @UniqueValue, it accepts two parameters (methodName & className) .
@UniqueValue(
methodName = "findByAccountName",
className = "com.tech.mkblogs.account.service.AccountService"message = "{account.name.alreadyexists}",
groups = Default.class
) @Documented@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Constraint(validatedBy = UniqueValueValidator.class)
public @interface UniqueValue {
Stringmessage() default"Already exists value";
Class<?>[] groups() default {};
Class<? extendsPayload>[] payload() default {}; StringmethodName();
StringclassName();
}publicclassUniqueValueValidatorimplementsConstraintValidator<UniqueValue, Object>{
privateStringclassName;
privateStringmethodName;
@Overridepublicvoidinitialize(UniqueValueunique) {
this.methodName = unique.methodName();
this.className = unique.className();
}
@OverridepublicbooleanisValid(Objectvalue, ConstraintValidatorContextcontext) {
//1 Check the Object type as per requirement//2 using java reflection call the service and check value exists or not
}
}First validator creates object of service class then calls the given method and return db values. After that validator check size is empty or not. It size is not empty then validator should return true otherwise false. We can reuse the same validator for other object by passing different class and method
@UniqueValue(
methodName = "findByUserName",
className = "com.tech.mkblogs.user.service.UserService",
message = "{user.username.alreadyexists}",
groups = Default.class
)@UniqueValue(
methodName = "<<methodName>>",
className = "<<className>>",
message = "<<messagee>>",
groups = <<groupnameifrequired>>
)By default, constraints are evaluated in no particular order, regardless of which groups they belong to. In some situations, however, it is useful to control the order constraints are evaluated.
In order to implement custom validation order we just need to define an interface and annotate it with @GroupSequence, defining the order in which the groups have to be validated. If at least one constraint fails in a sequenced group none of the constraints of the following groups in the sequence get validated.
@GroupSequence is a class level annotation
@GroupSequence({First.class,Second.class,Third.class,AccountDTO.class})And flow charts like this:
graph TD
A[First.class] --> B{validation of the group}
B -->|No Validation Messages| C[Second.class]
B -->|Validation Failed| D((STOP - Send error messages to UI))
C --> E{validation of the group}
E -->|No Validation Messages| G[Third.class]
E -->|Validation Failed| F((STOP - Send error messages to UI))
G --> H{validation of the group}
H -->|No Validation Messages| J[AccountDTO.class]
H -->|Validation Failed| I((STOP - Send error messages to UI))
J --> K{validation of the group}
K -->|No Validation Messages| M[Success]
K -->|Validation Failed| L((STOP - Send error messages to UI))
publicclassResponseDTO <S,E> {
privateBooleanisError;
privateSsuccessObject;
privateEerrorObject; }A controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation driven interceptor.
@ControllerAdvicepublicclassRestExceptionHandlerextendsResponseEntityExceptionHandler {
//define all exception classes here//sample code@OverrideprotectedResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidExceptionex,
HttpHeadersheaders,
HttpStatusstatus,
WebRequestrequest) {
ResponseDTO<Object, List<ErrorObject>> responseDTO = newResponseDTO<>();
responseDTO.setIsError(true);
List<ErrorObject> errorList = newArrayList<ErrorObject>();
if(ex.getBindingResult().hasGlobalErrors()) { List<ObjectError> list = ex.getBindingResult().getGlobalErrors();
errorList.addAll(list.stream()
.map(error -> ErrorObject.of(error.getObjectName(), error.getObjectName(), error.getDefaultMessage()))
.collect(Collectors.toList()));
}else {
if(ex.getBindingResult().hasErrors()) {
List<FieldError> list = ex.getBindingResult().getFieldErrors(); errorList.addAll(list.stream()
.map(error -> ErrorObject.of(error.getField(), error.getObjectName(), error.getDefaultMessage()))
.collect(Collectors.toList()));
}
} responseDTO.setErrorObject(errorList);
returnResponseEntity.ok().body(responseDTO);
}
}