-
Notifications
You must be signed in to change notification settings - Fork 424
JSR 380 BeanValidation
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 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.
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
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