Java Bean Validation Basics
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.
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.
Reference: | Java Bean Validation Basics from our JCG partner Michael Remijan at the Michael Remijan blog. |