Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’re going to review Hibernate Validator constraints, which are built into Hibernate Validator but are outside the Bean Validation spec.

For a recap of Bean Validation, please refer to our article on Java Bean Validation Basics.

2. Hibernate Validator Setup

At the very least, we should add Hibernate Validator to our dependencies:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.16.Final</version>
</dependency>

Note that Hibernate Validator does not depend on Hibernate, the ORM, which we’ve covered in many other articles.

Additionally, some of the annotations that we’ll introduce only apply if our project makes use of certain libraries. So, for each one of those, we’ll indicate the necessary dependencies.

3. Validating Money-related Values

3.1. Validating Credit Card Numbers

Valid credit card numbers must satisfy a checksum, which we compute using Luhn’s Algorithm. The @CreditCardNumber constraint succeeds when a string satisfies the checksum.

@CreditCardNumber does not perform any other check on the input string. In particular, it doesn’t check the length of the input. Therefore, it can only detect numbers that are invalid due to a small typo.

Note that, by default, the constraint fails if the string contains characters which aren’t digits, but we can tell it to ignore them:

@CreditCardNumber(ignoreNonDigitCharacters = true)
private String lenientCreditCardNumber;

Then, we can include characters such as spaces or dashes:

validations.setLenientCreditCardNumber("7992-7398-713");
constraintViolations = validator.validateProperty(validations, "lenientCreditCardNumber");
assertTrue(constraintViolations.isEmpty());

3.2. Validating Monetary Values

The @Currency validator checks whether a given monetary amount is in the specified currency:

@Currency("EUR")
private MonetaryAmount balance;

The class MonetaryAmount is part of Java Money. Therefore, @Currency only applies when a Java Money implementation is available.

Once we have set Java Money up correctly, we can check the constraint:

bean.setBalance(Money.of(new BigDecimal(100.0), Monetary.getCurrency("EUR")));
constraintViolations = validator.validateProperty(bean, "balance");
assertEquals(0, constraintViolations.size());

4. Validating Ranges

4.1. Numeric and Monetary Ranges

The bean validation specification defines several constraints which we can enforce on numeric fields. Besides those, Hibernate Validator provides a handy annotation, @Range, that acts as a combination of @Min and @Max, matching a range inclusively:

@Range(min = 0, max = 100)
private BigDecimal percent;

Like @Min and @Max, @Range is applicable on fields of primitive number types and their wrappers; BigInteger and BigDecimalString representations of the above, and, finally, MonetaryValue fields.

4.2. Duration of Time

In addition to standard JSR 380 annotations for values that represent points in time, Hibernate Validator includes constraints for Durations as well. Make sure to check out the Period and Duration classes of Java Time first.

So, we can enforce minimum and maximum durations on a property:

@DurationMin(days = 1, hours = 2)
@DurationMax(days = 2, hours = 1)
private Duration duration;

Even if we didn’t show them all here, the annotation has parameters for all units of time from nanoseconds to days.

Please note that, by default, minimum and maximum values are inclusive. That is, a value which is exactly the same as the minimum or the maximum will pass validation.

If we want boundary values to be invalid, instead, we define the inclusive property to be false:

@DurationMax(minutes = 30, inclusive = false)

5. Validating Strings

5.1. String Length

We can use two slightly different constraints to enforce that a string is of a certain length.

Generally, we’ll want to ensure a string’s length in characters – the one we measure with the length method – is between a minimum and a maximum. In that case, we use @Length on a String property or field:

@Length(min = 1, max = 3)
private String someString;

However, due to the intricacies of Unicode, sometimes the length in characters and the length in code points differ. When we want to check the latter, we use @CodePointLength:

@CodePointLength(min = 1, max = 3)
private String someString;

For example, the string “aa\uD835\uDD0A” is 4 characters long, but it contains only 3 code points, so it’ll fail the first constraint and pass the second one.

Also, with both annotations, we can omit the minimum or the maximum value.

5.2. Checks on Strings of Digits

We’ve already seen how to check that a string is a valid credit card number. However, Hibernate Validator includes several other constraints for strings of digits.

The first one we’re reviewing is @LuhnCheck. This is the generalized version of @CreditCardNumber, in that it performs the same check, but allows for additional parameters:

@LuhnCheck(startIndex = 0, endIndex = Integer.MAX_VALUE, checkDigitIndex = -1)
private String someString;

Here, we’ve shown the default values of the parameters, so the above is equivalent to a simple @LuhnCheck annotation.

But, as we can see, we can perform the check on a substring (startIndex and endIndex) and tell the constraint which digit is the checksum digit, with -1 meaning the last one in the checked substring.

Other interesting constraints include the modulo 10 check (@Mod10Check) and the modulo 11 check (@Mod11Check), which are typically used for barcodes and other codes such as ISBN.

However, for those specific cases, Hibernate Validator happens to provide a constraint to validate ISBN codes, @ISBN, as well as an @EAN constraint for EAN barcodes.

5.3. URL and HTML Validation

The @Url constraint verifies that a string is a valid representation of a URL. Additionally, we can check that specific component of the URL has a certain value:

@URL(protocol = "https")
private String url;

We can thus check the protocol, the host and the port. If that’s not sufficient, there’s a regexp property that we can use to match the URL against a regular expression.

We can also verify that a property contains “safe” HTML code (for example, without script tags):

@SafeHtml
private String html;

@SafeHtml uses the JSoup library, which must be included in our dependencies.

We can tailor the HTML sanitization to our needs using built-in tag whitelists (the whitelist property of the annotation) and including additional tags and attributes (the additionalTags and additionalTagsWithAttributes parameters).

6. Other Constraints

Let’s mention briefly that Hibernate Validator includes some country and locale-specific constraints, in particular for some Brazilian and Polish identification numbers, taxpayer codes and similar. Please refer to the relevant section of the documentation for a full list.

Also, we can check that a collection does not contain duplicates with @UniqueElements.

Finally, for complex cases not covered by existing annotations, we can invoke a script written in a JSR-223 compatible scripting engine. We’ve, of course, touched on JSR-223 in our article about Nashorn, the JavaScript implementation included in modern JVMs.

In this case, the annotation is at the class level, and the script is invoked on the entire instance, passed as the variable _this:

@ScriptAssert(lang = "nashorn", script = "_this.valid")
public class AdditionalValidations {
    private boolean valid = true;
    // standard getters and setters
}

Then, we can check the constraint on the whole instance:

bean.setValid(false);
constraintViolations = validator.validate(bean);
assertEquals(1, constraintViolations.size());

7. Conclusion

In this article, we’ve listed the constraints in Hibernate Validator that go beyond the minimal set defined in the Bean Validation specification.

The implementation of all these examples and code snippets can be found over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of