javax.validation defines a number of Bean Validation Constraints for values of most commonly used types. In this post we’ll show how to use each of them!
Constrained Bean
For illustration purposes we’re going to use the following bean, where each field has been annotated with a separate javax.validation built-in constraint:
package com.farenda.javax.validation; import javax.validation.constraints.*; import java.math.BigDecimal; import java.util.Date; import java.util.Set; public class ConstrainedBean { // The fields are with their tests below }
Declarations of all the fields are next to their tests to not make you scroll all the time. :-)
Validation of a single field
To simplify testing of Bean Validation constraints we’ll use Validator.validateValue(Class bean, String propName, Object value), which takes a constrained bean and name of property to fill with given value. Then it validates only this property.
We’ll create a wrapper around this method to further simply things and get only validation status. Invalid values will be printed to StdOut:
package com.farenda.javax.validation; import com.google.common.collect.Sets; import org.junit.Test; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import java.util.Date; import java.util.Set; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class ConstrainedBeanTest { private static Validator validator = Validation .buildDefaultValidatorFactory().getValidator(); private boolean isValid(String propertyName, Object value) { Set<ConstraintViolation<ConstrainedBean>> violations = validator.validateValue( ConstrainedBean.class, propertyName, value); if (violations.size() == 0) { return true; } violations.forEach(v -> System.out.printf("%s = %s: %s%n", v.getPropertyPath(), value, v.getMessage())); return false; } // ...
Null is a valid value
The common theme across all built-in constraints (except @NotNull of course) is that null is a valid value and passes validation. Now, when you think about this, it does make sense. This allows us to declare optional fields and, if we want, we can always force non-null values using @NotNull constraint.
Boolean properties validation
Let’s start with assertions on boolean properties. They work for boolean and Boolean.
AssertTrue a true constraint
@AssertTrue checks if a property is true.
// ConstrainedBean field: @AssertTrue public Boolean strategic; @Test public void checkAssertTrue() { assertTrue(isValid("strategic", true)); assertTrue(isValid("strategic", null)); assertFalse(isValid("strategic", false)); }
Test result:
strategic = false: must be true
AssertFalse constraint
@AssertFalse is a sibling to @AssertTrue and expects the annotated property to be false:
@AssertFalse public Boolean withDebts; @Test public void checkAssertFalse() { assertFalse(isValid("withDebts", true)); assertTrue(isValid("withDebts", null)); assertTrue(isValid("withDebts", false)); }
Test output:
withDebts = true: must be false
Nullness checks
javax.validation provides two annotations to validate nullness.
NotNull constraint
@NotNull is very often used, because by default null is accepted as valid value in other Bean Validation Constraints, so if we want to force non-null we have to add @NotNull annotation.
@NotNull public String title; @Test public void checkNotNull() { assertTrue(isValid("title", "")); assertTrue(isValid("title", "Catch 22")); assertFalse(isValid("title", null)); }
Validation output:
title = null: may not be null
Null constraint
@Null tell the validator that null value is expected, but it’s not used very often.
@Null public String translatedTitle; @Test public void checkNull() { assertFalse(isValid("translatedTitle", "")); assertFalse(isValid("translatedTitle", "Paragraf 22")); assertTrue(isValid("translatedTitle", null)); }
Validation output:
translatedTitle = : must be null translatedTitle = Paragraf 22: must be null
Date constraints
There are two built-in constraints for dates. They both work for properties of type java.util.Date and java.util.Calendar and both compare value of the annotated property against the current time of the JVM.
Past date Constraint
@Past // accepts also Calendar public Date publicationDate; @Test public void checkPast() { assertTrue(isValid("publicationDate", new Date(1))); assertTrue(isValid("publicationDate", null)); // 10 secs in the future from now: long in10Secs = System.currentTimeMillis() + SECONDS.toMillis(10); assertFalse(isValid("publicationDate", new Date(in10Secs))); }
@Past date validation result:
publicationDate = Sat Jan 28 22:31:03 CET 2017: must be in the past
Future date constraint
@Future // accepts also Calendar public Date nextReleaseDate; @Test public void checkFuture() { assertFalse(isValid("nextReleaseDate", new Date(1))); assertTrue(isValid("nextReleaseDate", null)); // 10 secs in the future from now: long in10Secs = System.currentTimeMillis() + SECONDS.toMillis(10); assertTrue(isValid("nextReleaseDate", new Date(in10Secs))); }
@Future date validation result:
nextReleaseDate = Thu Jan 01 01:00:00 CET 1970: must be in the future
Regular Expressions
Pattern constraint
The @Pattern constraint is very useful and works with any CharSequence. The specified regex pattern must follow java.util.regex.Pattern:
@Pattern(regexp = "\\d{2}-\\d{3}") public String postalCode; @Test public void checkPattern() { assertTrue(isValid("postalCode", "12-345")); assertTrue(isValid("postalCode", null)); assertFalse(isValid("postalCode", "")); assertFalse(isValid("postalCode", "ab-345")); assertFalse(isValid("postalCode", "12345")); assertFalse(isValid("postalCode", "123-456")); }
The output of the validations:
postalCode = : must match "\d{2}-\d{3}" postalCode = ab-345: must match "\d{2}-\d{3}" postalCode = 12345: must match "\d{2}-\d{3}" postalCode = 123-456: must match "\d{2}-\d{3}"
Numeric constraints
Javax Validation provides a handful of built-in validation constraints for numbers. As usual, you can combine constraints on a property.
Min value constraint
As expected @Min constraint allows to set allowed minimum value:
@Min(100) public int happyCustomers; @Test public void checkMin() { assertTrue(isValid("happyCustomers", null)); assertTrue(isValid("happyCustomers", 101)); assertFalse(isValid("happyCustomers", 20)); }
Validation output:
happyCustomers = 20: must be greater than or equal to 100
Both @Min and @Max can be set on types:
- BigDecimal
- BigInteger
- byte, short, int, long, and their wrappers.
Double and float may have partial (approximate) support by some Bean Validation providers, but in general these types are discouraged due to rounding errors.
Max value constraint
@Max sets allowed maximum value for the annotated property:
@Max(1) public int complaints; @Test public void checkMax() { assertTrue(isValid("complaints", null)); assertTrue(isValid("complaints", 1)); assertFalse(isValid("complaints", 2)); }
Violation of @Max constraint produces:
complaints = 2: must be less than or equal to 1
DecimalMin constraint
@DecimalMin allows to set precise lower bound on a number:
@DecimalMin("9.99") public BigDecimal minPrice; @Test public void checkDecimalMin() { assertTrue(isValid("minPrice", null)); assertTrue(isValid("minPrice", 10)); assertFalse(isValid("minPrice", 9.98)); }
Validation result:
minPrice = 9.98: must be greater than or equal to 9.99
Both @DecimalMin and @DecimalMax can be set on types:
- BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long, and their wrappers.
Double and float may have partial (approximate) support by some Bean Validation providers, but in general these types are discouraged due to rounding errors.
DecimalMax constraint
@DecimalMax is a twin of @DecimalMin, but allows to set precise upper bound on a number:
@DecimalMax("20.1234") public BigDecimal maxPrice; @Test public void checkDecimalMax() { assertTrue(isValid("maxPrice", null)); assertTrue(isValid("maxPrice", 20)); assertFalse(isValid("maxPrice", 42)); }
Validation result:
maxPrice = 42: must be less than or equal to 20.1234
Digits constraint
@Digits annotation allows to specify digits pattern for a property. Numbers are checked to have specified number of integer and fraction digits. Just look at the exmaple:
@Digits(integer = 1, fraction = 2) public BigDecimal price; @Test public void checkDigits() { assertTrue(isValid("price", null)); assertTrue(isValid("price", 2)); assertTrue(isValid("price", 9.99)); assertFalse(isValid("price", 1.955)); assertFalse(isValid("price", 10)); }
Violatione of the constraint on the number of digits:
price = 1.955: numeric value out of bounds (<1 digits>.<2 digits> expected) price = 10: numeric value out of bounds (<1 digits>.<2 digits> expected)
Notice the difference between @DecimalMax and @Digits – the first one checks upper bound, whereas @Digits constraint checks digit pattern (number of digits before and after the dot).
@Digits can be set on types:
- BigDecimal
- BigInteger
- CharSequence
- byte, short, int, long, and their wrappers.
Size of elements
Size constraint
@Size specifies lower and upper bounds (both inclusive) for number of elements a property may have. The constraint is quite flexible and supports the following types:
- CharSequence (checks it’s length)
- Collection (checks size)
- Map (checks size)
- Array (checks length).
Let’s look how to constraint a number of elements in a Set:
@Size(min = 1, max = 2) public Set<String> names; @Test public void checkSize() { assertTrue(isValid("names", null)); assertFalse(isValid("names", Sets.newHashSet())); assertTrue(isValid("names", Sets.newHashSet("a"))); assertTrue(isValid("names", Sets.newHashSet("a", "b"))); assertFalse(isValid("names", Sets.newHashSet("a", "b", "c"))); }
The above test produces the following output:
names = []: size must be between 1 and 2 names = [a, b, c]: size must be between 1 and 2
Combining validation constraints
Of course the constraints can be combined when necessary to get fine grined validations:
@Min(0) @Max(150) public int passengers;
References:
- Check out other topics in Bean Validation Tutorial
- JUnit Tutorial