Abstract
This post summarizes some quick and easy examples for the most common things you would want to do with the Java Beans Validation API (JSR 349, JSR 303). Remember, Beans Validation is independent of Java EE. Although it is built in as part of a Java EE compliant server, the API can also be used just as easily in a Java SE application. All these examples use Java SE.
Table of Contents
- Basics
- Custom Message Template
- Custom Message Template with Variable Replacement
- Custom Property Validator
- Custom Class Validator
- GroupSequence (Short Circuit)
Requirements
I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.
- Java 1.8.0_65_x64
- NetBeans 8.2
- Maven 3.0.5 (Bundled with NetBeans)
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.2.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Download
Visit my GitHub page https://github.com/mjremijan to see all of my open source projects. The code for this post is located at: https://github.com/mjremijan/thoth-beanvalidation
Basics
This example shows the basics of bean validation using the built-in, standard constraints and the built-in, standard validators.
Listing 1.1 - Bean to validate
package org.thoth.beanvalidation.basics;
import javax.validation.constraints.NotNull;
public class Widget {
@NotNull
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Listing 1.2 - How to validate
package org.thoth.beanvalidation.basics;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class WidgetTest {
protected Validator validator;
@Before
public void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void violations_size() {
// setup
Widget w = new Widget();
// action
Set<ConstraintViolation<Widget>> violations
= validator.validate(w);
// assert
Assert.assertEquals(1, violations.size());
}
@Test
public void violation_message() {
// setup
Widget w = new Widget();
// action
Set<ConstraintViolation<Widget>> violations
= validator.validate(w);
// assert
ConstraintViolation<Widget> v
= violations.stream().findFirst().get();
Assert.assertEquals("may not be null", v.getMessage());
}
@Test
public void violation_messageTemplate() {
// setup
Widget w = new Widget();
// action
Set<ConstraintViolation<Widget>> violations
= validator.validate(w);
// assert
ConstraintViolation<Widget> v
= violations.stream().findFirst().get();
Assert.assertEquals("{javax.validation.constraints.NotNull.message}", v.getMessageTemplate());
}
@Test
public void violation_propertyPath() {
// setup
Widget w = new Widget();
// action
Set<ConstraintViolation<Widget>> violations
= validator.validate(w);
// assert
ConstraintViolation<Widget> v
= violations.stream().findFirst().get();
Assert.assertEquals("name", v.getPropertyPath().toString());
}
}
Custom Message Template
This example shows how the built-in, standard constraints can be customized with a custom error message instead of using the built-in, standard error messages.
Listing 2.1 - ValidationMessages.properties
Candy.name.NotNull=A candy name is required.
Listing 2.2 - Bean to validate
package org.thoth.beanvalidation.custommessage;
import javax.validation.constraints.NotNull;
public class Candy {
@NotNull(message = "{Candy.name.NotNull}")
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Listing 2.3 - How to validate
package org.thoth.beanvalidation.custommessage;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class CandyTest {
protected static Validator validator;
@BeforeClass
public static void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void notnull_violation_message() {
// setup
Candy candy = new Candy();
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(candy);
// assert
ConstraintViolation<Candy> v
= violations.stream().findFirst().get();
Assert.assertEquals("A candy name is required.", v.getMessage());
}
@Test
public void notnull_violation_messageTemplate() {
// setup
Candy candy = new Candy();
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(candy);
// assert
ConstraintViolation<Candy> v
= violations.stream().findFirst().get();
Assert.assertEquals("{Candy.name.NotNull}", v.getMessageTemplate());
}
}
Custom Message Template with Variable Replacement
This example shows how the built-in, standard constraints can be configured with a custom error message which has variable values in the message which are replaced by bean validation at runtime. Examples of variables which can be replaced are the actual value which was validate and the min and max properties of a @Size
constraint.
Listing 3.1 - ValidationMessages.properties
Candy.name.Size.message=The candy name "${validatedValue}" is invalid. It must be between {min} and {max} characters long
Listing 3.2 - Bean to validate
package org.thoth.beanvalidation.variablereplacement;
import javax.validation.constraints.Size;
public class Candy {
private String name;
@Size(message = "{Candy.name.Size.message}", min=5, max=10)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Listing 3.3 - How to validate
package org.thoth.beanvalidation.variablereplacement;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.thoth.beanvalidation.variablereplacement.Candy;
public class CandyTest {
protected static Validator validator;
@BeforeClass
public static void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void does_the_constraint_have_the_correct_messageTemplate() {
// setup
Candy candy = new Candy();
candy.setName("");
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(candy);
// assert
ConstraintViolation<Candy> v
= violations.stream().findFirst().get();
Assert.assertEquals("{Candy.name.Size.message}", v.getMessageTemplate());
}
@Test
public void is_the_message_correct_if_size_is_too_small() {
// setup
Candy candy = new Candy();
candy.setName("foo");
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(candy);
// assert
ConstraintViolation<Candy> v
= violations.stream().findFirst().get();
Assert.assertEquals("The candy name \"foo\" is invalid. It must be between 5 and 10 characters long", v.getMessage());
}
@Test
public void is_the_message_correct_if_size_is_too_big() {
// setup
Candy candy = new Candy();
candy.setName("123456789|1");
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(candy);
// assert
ConstraintViolation<Candy> v
= violations.stream().findFirst().get();
Assert.assertEquals("The candy name \"123456789|1\" is invalid. It must be between 5 and 10 characters long", v.getMessage());
}
}
Custom Property Validator
This example shows how to create your own constraint and your own validator for a property of a class.
Listing 4.1 - ValidationMessages.properties
org.thoth.beanvalidation.propertyvalidator.Excludes.message=The value "${validatedValue}" is one of {value} which is forbidden.
Listing 4.2 - Constraint annotation
package org.thoth.beanvalidation.propertyvalidator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
@Target({
ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ExcludesValidator.class})
@Documented
public @interface Excludes {
String message() default "{org.thoth.beanvalidation.propertyvalidator.Excludes.message}";
Class[] groups() default {};
Class[] payload() default {};
String[] value() default {};
}
Listing 4.3 - Constraint validator
package org.thoth.beanvalidation.propertyvalidator;
import java.util.Arrays;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class ExcludesValidator
implements ConstraintValidator< Excludes, String> {
private List<String> excludeTheseValues;
@Override
public void initialize(Excludes arg) {
String[] strarr = arg.value();
if (strarr == null) {
strarr = new String[]{};
}
excludeTheseValues = Arrays.asList(strarr);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext cvc) {
if (excludeTheseValues.contains(value)) {
return false;
} else {
return true;
}
}
}
Listing 4.4 - Bean to validate
package org.thoth.beanvalidation.propertyvalidator;
public class Candy {
private String name;
public Candy(String name) {
this.name = name;
}
@Excludes({"foo", "bar", "shrubbery"})
public String getName() {
return name;
}
}
Listing 4.5 - How to validate
package org.thoth.beanvalidation.propertyvalidator;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.junit.Test;
public class CandyTest {
protected static Validator validator;
@BeforeClass
public static void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void a_non_excludeded_name_should_not_give_you_a_constraint_violation() {
// setup
Candy candy = new Candy("hershey");
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(candy);
// assert
assertEquals(0, violations.size());
}
@Test
public void do_you_get_a_constraint_violation_if_you_use_excluded_name_foo() {
// setup
Candy candy = new Candy("foo");
// action
ConstraintViolation<Candy> violation
= validator.validate(candy).iterator().next();
// assert
assertEquals("{org.thoth.beanvalidation.propertyvalidator.Excludes.message}", violation.getMessageTemplate());
assertEquals("The value \"foo\" is one of [foo, bar, shrubbery] which is forbidden.", violation.getMessage());
}
@Test
public void do_you_get_a_constraint_violation_if_you_use_excluded_name_bar() {
// setup
Candy candy = new Candy("bar");
// action
ConstraintViolation<Candy> violation
= validator.validate(candy).iterator().next();
// assert
assertEquals("{org.thoth.beanvalidation.propertyvalidator.Excludes.message}", violation.getMessageTemplate());
assertEquals("The value \"bar\" is one of [foo, bar, shrubbery] which is forbidden.", violation.getMessage());
}
@Test
public void do_you_get_a_constraint_violation_if_you_use_excluded_name_shrubbery() {
// setup
Candy candy = new Candy("shrubbery");
// action
ConstraintViolation<Candy> violation
= validator.validate(candy).iterator().next();
// assert
assertEquals("{org.thoth.beanvalidation.propertyvalidator.Excludes.message}", violation.getMessageTemplate());
assertEquals("The value \"shrubbery\" is one of [foo, bar, shrubbery] which is forbidden.", violation.getMessage());
}
}
Custom Class Validator
This example shows how to create your own constraint and your own validator which applies to an entire class.
Listing 5.1 - ValidationMessages.properties
org.thoth.beanvalidation.classvalidator.IdentificationExists.message=At least one of social security number, drivers license number, or passport number must exist.
Listing 5.2 - Constraint annotation
package org.thoth.beanvalidation.classvalidator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {IdentificationExistsValidator.class})
@Documented
public @interface IdentificationExists {
String message() default "{org.thoth.beanvalidation.classvalidator.IdentificationExists.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Listing 5.3 - Constraint validator
package org.thoth.beanvalidation.classvalidator;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IdentificationExistsValidator implements ConstraintValidator<IdentificationExists, Identification> {
@Override
public void initialize(IdentificationExists a) {}
@Override
public boolean isValid(Identification t, ConstraintValidatorContext cvc) {
boolean invalid =
Objects.equals(t.getDriversLicenseNumber(), null)
&&
Objects.equals(t.getPassportNumber(), null)
&&
Objects.equals(t.getSocialSecurityNumber(), null)
;
return !invalid;
}
}
Listing 5.4 - Bean to validate
package org.thoth.beanvalidation.classvalidator;
@IdentificationExists
public class Identification {
protected String socialSecurityNumber;
protected String driversLicenseNumber;
protected String passportNumber;
public String getSocialSecurityNumber() {
return socialSecurityNumber;
}
public void setSocialSecurityNumber(String socialSecurityNumber) {
this.socialSecurityNumber = socialSecurityNumber;
}
public String getDriversLicenseNumber() {
return driversLicenseNumber;
}
public void setDriversLicenseNumber(String driversLicenseNumber) {
this.driversLicenseNumber = driversLicenseNumber;
}
public String getPassportNumber() {
return passportNumber;
}
public void setPassportNumber(String passportNumber) {
this.passportNumber = passportNumber;
}
}
Listing 5.5 - How to validate
package org.thoth.beanvalidation.classvalidator;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class IdentificationTest {
protected Validator validator;
@Before
public void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void violation_if_all_are_missing() {
// setup
Identification id = new Identification();
// action
Set<ConstraintViolation<Identification>> violations
= validator.validate(id);
// assert
ConstraintViolation<Identification> v
= violations.stream().findFirst().get();
Assert.assertEquals("At least one of social security number, drivers license number, or passport number must exist.", v.getMessage());
}
@Test
public void no_violation_if_social_security_number_exists() {
// setup
Identification id = new Identification();
id.setSocialSecurityNumber("a");
// action
Set<ConstraintViolation<Identification>> violations
= validator.validate(id);
// assert
Assert.assertEquals(0, violations.size());
}
@Test
public void no_violation_if_drivers_license_number_exists() {
// setup
Identification id = new Identification();
id.setDriversLicenseNumber("a");
// action
Set<ConstraintViolation<Identification>> violations
= validator.validate(id);
// assert
Assert.assertEquals(0, violations.size());
}
@Test
public void no_violation_if_passport_number_exists() {
// setup
Identification id = new Identification();
id.setPassportNumber("a");
// action
Set<ConstraintViolation<Identification>> violations
= validator.validate(id);
// assert
Assert.assertEquals(0, violations.size());
}
}
GroupSequence (Short Circuit)
This example shows how to use @GroupSequence
as a short circuit when doing validation. This means if the 1st round of validations do not pass, then validation is “short circuited” and the 2nd round of validations is not performed.
By default, all bean validation constraints are put into a “Default” group sequence. However, by putting a @GroupSequence
on a class (like shown below) the “Default” group sequence is redefined just for that class. With the @GroupSequence
on a class below, what it basically does is that during beans validation the 1st operation is to validate all constraints in the class that aren’t specifically assigned a group. That would be the @NotNull
constraint first. If all of those are OK, then the 2nd operation is to validate all constraints that are in the Second.class
group. That would be the @Size
constraint. If all of those are OK, then 3rd operation is to validate all of the constraints that are in the Third.class
group. That would be the @Pattern
constraint. If at any time a group fails to validate, validation is “short circuited” and validation goes no farther.
Listing 6.1 - Bean to validate
package org.thoth.beanvalidation.groupsequence;
import javax.validation.GroupSequence;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
@GroupSequence({Candy.class, Candy.Second.class, Candy.Third.class})
public class Candy {
protected interface Second {}
protected interface Third {}
private String name;
@NotNull()
@Size(min=4, max=10, groups = Second.class )
@Pattern(regexp = "[a-z]", groups = Third.class)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Listing 6.2 - How to validate
package org.thoth.beanvalidation.groupsequence;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
public class CandyTest {
private Validator validator;
@Before
public void before() {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void short_circuits_first_if_null() {
// setup
Candy w = new Candy();
// action
Set<ConstraintViolation<Candy>> violations
//= validator.validate(w, CheckGroupSequence.class);
= validator.validate(w);
// assert
assertEquals(1, violations.size());
assertEquals("may not be null", violations.iterator().next().getMessage());
}
@Test
public void short_circut_if_size_is_in_violation() {
// setup
Candy w = new Candy();
w.setName("foo");
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(w);
// assert
assertEquals(1, violations.size());
assertEquals("size must be between 4 and 10", violations.iterator().next().getMessage());
}
@Test
public void short_circuit_if_pattern_is_in_violation() {
// setup
Candy w = new Candy();
w.setName("SHRUBBERY");
// action
Set<ConstraintViolation<Candy>> violations
= validator.validate(w);
// assert
assertEquals(1, violations.size());
assertEquals("must match \"[a-z]\"", violations.iterator().next().getMessage());
}
}
Summary
Beans validation is a powerful API, especially since it can be used within a Java EE server or in stand-alone Java SE applications. This is just a very short summary of the basics of the beans validation API, but, typically, it is enough to cover most questions developers have about how to use it.
No comments:
Post a Comment