Java Programming Tutorials

Java programming tutorials with many code examples!

Bean Validation Constraints

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:

Share with the World!