Skip to content

JSR 380 BeanValidation

Remko Popma edited this page Feb 8, 2019 · 3 revisions

JSR-380 BeanValidation in a picocli Application

Picocli does not offer an API for validating constraints on options or positional parameters, other than the type conversion. As of version 3.9.3, the recommended way to do validation on command line arguments is to either code the validation rules before the business logic of your application or to use annotated setter methods (see ValidationExample in the manual). An alternative is to use JSR-380 BeanValidation.

JSR 380

JSR 380 is a specification of the Java API for bean validation, part of JavaEE and JavaSE, which ensures that the properties of a bean meet specific criteria, using annotations such as @NotNull, @Min, and @Max.

This version requires Java 8 or higher, and takes advantage of new features added in Java 8 such as type annotations, and supports new types like Optional and LocalDate.

For full information on the specifications, see the JSR 380.

Example

Dependencies

JSR 380 needs quite a few dependencies:

  • javax.validation:validation-api:2.0.0.Final - contains the beans validation API

  • org.hibernate.validator:hibernate-validator:6.0.2.Final - Hibernate Validator is the reference implementation of the validation API

  • org.hibernate.validator:hibernate-validator-annotation-processor:6.0.2.Final

  • javax.el:javax.el-api:3.0.0 - Expression Language API - provides support for variable interpolation, allowing expressions inside the violation messages

  • org.glassfish.web:javax.el:2.2.6 - GlassFish provides the reference EL implementation

Example Application

package picocli.examples.jsr380.beanvalidation;

import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Spec;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.*;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;

// Example inspired by https://www.baeldung.com/javax-validation
public class User implements Runnable {

    @NotNull(message = "Name cannot be null")
    @Option(names = {"-n", "--name"}, description = "mandatory")
    private String name;

    @AssertTrue(message = "working must be true")
    @Option(names = {"-w", "--working"}, description = "Must be true")
    private boolean working;

    @Size(min = 10, max = 200, message
            = "About Me must be between 10 and 200 characters")
    @Option(names = {"-m", "--aboutMe"}, description = "between 10-200 chars")
    private String aboutMe;

    @Min(value = 18, message = "Age should not be less than 18")
    @Max(value = 150, message = "Age should not be greater than 150")
    @Option(names = {"-a", "--age"}, description = "between 18-150")
    private int age;

    @Email(message = "Email should be valid")
    @Option(names = {"-e", "--email"}, description = "valid email")
    private String email;

    @Option(names = {"-p", "--preferences"}, description = "not blank")
    List<@NotBlank String> preferences;

    @Option(names = {"-d", "--dateOfBirth"}, description = "past")
    private LocalDate dateOfBirth;

    @Spec
    CommandSpec spec;

    public User() {
    }

    public Optional<@Past LocalDate> getDateOfBirth() {
        return Optional.ofNullable(dateOfBirth);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", working=" + working +
                ", aboutMe='" + aboutMe + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", preferences=" + preferences +
                ", dateOfBirth=" + dateOfBirth +
                '}';
    }

    public static void main(String... args) {
        CommandLine.run(new User(), args);
    }

    @Override
    public void run() {
        validate();

        // ... now run the business logic
    }

    private void validate() {
        System.out.println(spec.commandLine().getParseResult().originalArgs());
        System.out.println(this);

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<User>> violations = validator.validate(this);

        if (!violations.isEmpty()) {
            String errorMsg = "";
            for (ConstraintViolation<User> violation : violations) {
                errorMsg += "ERROR: " + violation.getMessage() + "\n";
            }
            throw new ParameterException(spec.commandLine(), errorMsg);
        }
    }
}

Now let’s run this application with some invalid input:

picocli.examples.jsr380.beanvalidation.User -d 2019-03-01 -n Remko -p "" -p a -w -e remkop@yahoo@com --aboutMe about

We get the following output to STDOUT:

[-d, 2019-03-01, -n, Remko, -p, , -p, a, -w, -e, remkop@yahoo@com, --aboutMe, about]
User{name='Remko', working=true, aboutMe='about', age=0, email='remkop@yahoo@com', preferences=[, a], dateOfBirth=2019-03-01}

And the following output to STDERR:

Feb 08, 2019 5:11:59 PM org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 6.0.2.Final
ERROR: Age should not be less than 18
ERROR: must be a past date
ERROR: must not be blank
ERROR: About Me must be between 10 and 200 characters
ERROR: Email should be valid

Usage: <main class> [-w] [-a=<age>] [-d=<dateOfBirth>] [-e=<email>]
                    [-m=<aboutMe>] [-n=<name>] [-p=<preferences>]...
  -a, --age=<age>           between 18-150
  -d, --dateOfBirth=<dateOfBirth>
                            past
  -e, --email=<email>       valid email
  -m, --aboutMe=<aboutMe>   between 10-200 chars
  -n, --name=<name>         mandatory
  -p, --preferences=<preferences>
                            not blank
  -w, --working             Must be true