spring webflux validation

Spring WebFlux Validation

Overview:

In this tutorial, I would like to show you Spring WebFlux Validation for validating the beans / request inputs.

Spring WebFlux Validation:

When we expose our APIs using WebFlux, we might have a set of constraints before accepting any request for processing. For ex: Field should not be null, age should be within the given range etc. Otherwise it could be considered as a bad request!

As part of Java EE 8 standard, we have a set of annotations which could be used to validate the request object. Lets see how it works with Spring WebFlux.

Sample Application:

To demo this, Lets consider a simple application which accepts user information. We have below fields and constraints. If the constraints are met, we would register the user and respond with 200 success code. Otherwise we would be return 400 bad request with error message.

Field Description
firstName can be null
lastName can NOT be null
age min = 10
max = 50
email email address should NOT contain any numbers. Pattern is ([a-z])+ @ ([a-z]) .com

Project Setup:

Lets setup our Spring WebFlux project with below dependencies.

spring webflux validation

User DTO:

Lets create the UserDTO class for user registration process. We add the constraints & corresponding error messages as shown here. We can also modify to retrieve the message from property files instead of hard coding. We will discuss that later.

@Data
@ToString
public class UserDto {

    private String firstName;

    @NotNull(message = "Last name can not be empty")
    private String lastName;

    @Min(value = 10, message = "Required min age is 10")
    @Max(value = 50, message = "Required max age is 50")
    private int age;

    @Pattern(regexp = "([a-z])+@([a-z])+\\.com", message = "Email should match the pattern a-z @ a-z .com")
    private String email;

}

Service:

This is the service class which simply prints the user details on the console / save it to the DB if it is valid.

@Service
public class UserService {

    @AssertTrue
    public Mono<UserDto> registerUser(Mono<UserDto> userDtoMono){
        return userDtoMono
                .doOnNext(System.out::println);
    }

}

Controller:

We expose a POST API for user registration. Make a note of the @Valid annotation which ensures that given input is validated before invoking the service method.

@RestController
@RequestMapping("user")
public class RegistrationController {

    @Autowired
    private UserService userService;

    @PostMapping("register")
    public Mono<UserDto> register(@Valid @RequestBody Mono<UserDto> userDtoMono){
        return this.userService.registerUser(userDtoMono);
    }

}

Spring WebFlux Validation – Demo 1:

  • Now if we send a valid request like this, we get the success 200 response code.
{
    "firstName": "vins",
    "lastName": "vins",
    "age": 30,
    "email": "abcd@abcd.com"
}

spring webflux validation

  • If we send the request as shown here which does not satisfy our requirement, then we get 400 bad request response code without any meaningful information.
{
    "firstName": "vins",
    "lastName": "vins",
    "age": 60,
    "email": "abcd@abcd.com"
}

spring webflux validation

 

@ControllerAdvice:

To respond with appropriate error messages, Lets use controller advice as shown below. Here we catch the WebExchangeBindException, find the all the errors for which bean validation failed and retrieve the error messages.

@ControllerAdvice
public class ValidationHandler {

    @ExceptionHandler(WebExchangeBindException.class)
    public ResponseEntity<List<String>> handleException(WebExchangeBindException e) {
        var errors = e.getBindingResult()
                .getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        return ResponseEntity.badRequest().body(errors);
    }

}

It gives the appropriate error messages as shown below.

Messages.Properties:

Instead of hard coding the messages in the class level, we might want to retrieve the messages from a property file. Lets update our UserDTO as shown here.

@Data
@ToString
public class UserDto {

    private String firstName;

    @NotNull(message = "{lastname.not.null}")
    private String lastName;

    @Min(value = 10, message = "{age.min.requirement}")
    @Max(value = 50, message = "{age.max.requirement}")
    private int age;

    @Pattern(regexp = "([a-z])+@([a-z])+\\.com", message = "{email.pattern.mismatch}")
    private String email;

}

The corresponding messages for those keys are present in a property file name messages.properties placed under src/main/resources.

age.min.requirement=Age min requirement is 10
age.max.requirement=Age max requirement is 50
lastname.not.null=Last name can not be empty
email.pattern.mismatch=Email should match pattern abcd@abcd.com

This setup requires few additional bean configurations.

@Configuration
public class MessageConfig {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:messages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public LocalValidatorFactoryBean validatorFactoryBean(MessageSource messageSource) {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
        localValidatorFactoryBean.setValidationMessageSource(messageSource);
        return localValidatorFactoryBean;
    }

}

Now Spring WebFlux validation works just fine by reading the messages from the property files.

Conditional Validation:

There could be cases in which we might want to validate based on conditions. For ex: If the user is from US, then SSN is mandatory. For other countries, It is not mandatory!

Field Description
firstName can be null
lastName can NOT be null
age min = 10
max = 50
email email address should NOT contain any numbers. Pattern is ([a-z])+ @ ([a-z]) .com
country can NOT be null
ssn mandatory only if the country is US
  • To do this – lets first create an annotation.
@Documented
@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = SSNConstraintValidator.class)
public @interface ValidSSN {
    String message() default "{ssn.not.null}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
  • Our UserDTO is updated as shown here. Make a note of the @ValidSSN on the UserDTO class.
@Data
@ToString
@ValidSSN
public class UserDto {

    private String firstName;

    @NotNull(message = "{lastname.not.null}")
    private String lastName;

    @Min(value = 10, message = "{age.min.requirement}")
    @Max(value = 50, message = "{age.max.requirement}")
    private int age;

    @Pattern(regexp = "([a-z])+@([a-z])+\\.com", message = "{email.pattern.mismatch}")
    private String email;

    @NotNull(message = "{country.not.null}")
    private String country;

    private Integer ssn;

}
  • SSN Constraint validator which is custom conditional constraint validaiton.
public class SSNConstraintValidator implements ConstraintValidator<ValidSSN, UserDto> {

    @Override
    public boolean isValid(UserDto userDto, ConstraintValidatorContext constraintValidatorContext) {
        if("US".equals(userDto.getCountry()))
            return Objects.nonNull(userDto.getSsn());
        return true;
    }

}
  • messages properties.
age.min.requirement=Age min requirement is 10
age.max.requirement=Age max requirement is 50
lastname.not.null=Last name can not be empty
email.pattern.mismatch=Email should match pattern abcd@abcd.com
country.not.null=Country can not be empty
ssn.not.null=SSN can not be empty

Spring WebFlux Validation – Demo 2:

  • I send the below request w/o SSN for the user whose country is India.

  • If I change the country to US, then I get the error saying that SSN can not be empty.

 

Summary:

We were able to successfully demonstrate Spring WebFlux Validation and conditional validation as well.

The source code is available here.

Read more on Spring WebFlux.

Happy coding 🙂

 

Share This:

4 thoughts on “Spring WebFlux Validation

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.