Showing posts with label bean validation. Show all posts
Showing posts with label bean validation. Show all posts

July 13, 2017

Java Bean Validation Basics

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

  1. Basics
  2. Custom Message Template
  3. Custom Message Template with Variable Replacement
  4. Custom Property Validator
  5. Custom Class Validator
  6. 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.

January 21, 2015

Create Your Own Java Bean Validator with a Custom Element

Abstract
The purpose of this article is to demonstrate how to do two things.  The first is how to create your own Java bean validator as specified by JSR 349: Bean Validation 1.1.  The second is adding your own custom element to this validator so as to effect or define the validator's behavior.

System Requirements
This example was developed and run using the following system setup. If yours is different, it may work but no guarantees.
  • JDK 1.7.0_11
  • Eclipse Luna
  • Maven 3.2.1 (Embedded with Eclipse)
Maven Dependencies
The following dependencies are needed in your pom.xml to work with bean validation:

javax.validation
validation-api
1.1.0.Final


org.hibernate
hibernate-validator
5.1.2.Final


com.fasterxml
classmate
1.1.0

What is Bean Validation
JSR 349: Bean Validation 1.1 is the Java standard allowing you to develop reusable constraints and validators to ensure your application's data meets its business requirements. This may sound complicated, but it's actually very simple. Suppose your application is required to get a user's email address before successfully registering them for a new account. Traditionally, a developer would need to manually code this business requirement. Using JSR 349, the same business requirement can be satisfied using a reusable @NotNull constraint from the bean validation framework. Listing 1 demonstrates this.

Listing 1: Validate user's email address is not null
public class User {
@NotNull
protected String email;
// class continues...
}

As you can see from listing 1 the @NotNull constraint and the JSR 349 Bean Validation framework are responsible for validating the email address.  Additionally, the @NotNull constraint has no specific ties to email address meaning it can be reused to validate a users first name, last name, date of birth, or whatever data your business requirements say the user must provide.

The bean validation framework is very powerful in that it's also extensible. @NotNull is a very common constraint and as such is provided by the framework. However your application will no doubt have very specific business requirements which the built-in constraints cannot handle. Let's look at an example of such a requirement and how we can build our own constraints and validators to handle it.

Specific Business Requirement
Suppose your application needs to interact with the computer's file system. The user must select a directory but in order to guarantee your application does not inadvertently modify any existing files, it's required that the directory the user selects must be completely empty. This is a good example of data validation that is very specific to your application and which the bean validation framework does not handle out of the box. Remember though the bean validation framework is extensible. It's possible to create a constraint and validator to satisfy this business requirement.  Let's first look at creating the constraint.

Constraint
A bean validation constraint is a simple annotation. Listing 2 shows a @DirectoryIsEmpty validation constraint annotation we'll use to satisfy our application's business requirement.

Listing 2: DirectoryIsEmpty bean validation constraint
package org.ferris.validation.constraints;

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;
import org.ferris.validation.validator.DirectoryIsEmptyValidator;

@Target({ 
   ElementType.TYPE, ElementType.ANNOTATION_TYPE
 , ElementType.METHOD, ElementType.FIELD
 , ElementType.PARAMETER, ElementType.CONSTRUCTOR })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { DirectoryIsEmptyValidator.class })
@Documented
public @interface DirectoryIsEmpty {
    String message() default "{org.ferris.validation.constraints.DirectoryIsEmpty.message}";

    Class[] groups() default { };

    Class[] payload() default { };
    
    String[] excludes() default {};
}

Let's take a look at this constraint in detail.

#12 The @Target annotation defines what elements the @DirectoryIsEmpty can be put on.  In general, the annotation can either be put at the class-level, property-level, or parameter-level.  At the class-level, the validation occurs on the class as a whole. This is a way to validated properties which go together, like password and verifiedPassword properties need to be the same. At the property-level, the annotation can either go on the property or on the getter method for the property. At the parameter-level, the annotation goes on a constructor parameter or a method parameter. Typically the @Valid annotation is used at this level so the bean validation framework can validate properties before executing the constructor or method.

#17 The @Constraint annotation tells the bean validation framework which class is responsible for validating this constraint.  Here it defines the DirectoryIsEmptyValidator.class.

#19 @interface DirectoryIsEmpty is the standard way to define an annotation.

#20 The message element is required on bean validation constraint annotations. This element is used by the framework to determine what error message should be displayed if validation fails. You can hard code a plain text message, or, as is seen here, enter a resource bundle key as "{key_value}". By convention, the key value is the fully-qualified-class-name + ".message". The bean validation framework will look in the class path for a ValidationMessages.properties file.  This file is located at the root of the class path.

#22 The groups element is required on bean validation constraint annotations. This is used to group validation constraints together. For example, a field may have four validation constraints divided into three different groups. The @GroupSequence annotation can be used to define the sequence of the validation constraints.  This is helpful if a constraint validation needs to happen in a certain order.

#24 The payload element is required on bean validation constraint annotations, however it is not directly used by the bean validation framework itself. If a payload is present and a constraint violation occurs, the bean validation framework will put the payload into the ConstraintViolation#ConstraintDescriptor object for later use. If a payload is not present, the bean validation framework puts nothing into the object. A payload is a way to pass additional information about the constraint violation to the UI layer (or to any code) responsible for handling the violation. For example, a severity can be assigned to the payload to change the alerting level handling the violation.

#26 The excludes element is custom to this annotation. Just like any annotation, you are able to create your own custom elements in order to pass additional information to the validator to customize the validation. In this example, the excludes element is a String[] and it is used to define the names of directories which are excluded from the constraint validation. For example, suppose you have @DirectoryIsEmpty(excludes={"foo", "bar"}). At validation, the directory will NOT be checked if the name of the directory is either "foo" or "bar".

A constraint annotation is only the first part to creating your own bean validator. As seen on line #17, a class to do the validation is needed. In this example it is DirectoryIsEmptyValidator.class.  Let's take a look at this class next.

Validator
A validator class is the next piece you need to creating your own bean validation after creating the validation constraint annotation.  The annotation is used on whatever class, property, or parameter you want to validate. The validator class is responsible for doing the actual validation. Given our @DirectoryIsEmpty validation constraint annotation, listing 3 shows its validator

Listing 3: DirectoryIsEmptyValidator validator
package org.ferris.validation.validator;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.ferris.validation.constraints.DirectoryIsEmpty;

public class DirectoryIsEmptyValidator 
implements ConstraintValidator< DirectoryIsEmpty, File > {

 private List< String > excludes;
 
 @Override
 public void initialize(DirectoryIsEmpty arg0) {
  String [] strarr = arg0.excludes();
  if (strarr == null) {
   strarr = new String[]{};
  }
  excludes = Arrays.asList(strarr);
 }

 @Override
 public boolean isValid(File file, ConstraintValidatorContext context)
 {
  if (excludes.contains(file.getName())) {
   return true;
  }
  File [] files = file.listFiles();
  if (files != null && files.length > 0) {
   return false;
  } else {
   return true;
  }
 }
}

Let's take a look at this validator in more detail.

#11 Here we see the class implements the ConstraintValidator interface with the specific generics being DirectoryIsEmpty, and FileDirectoryIsEmpty obviously refers to the @DirectoryIsEmpty validation constraint annotation. File defines the @DirectoryIsEmpty annotation must be put on a class that extends File or on a File property or parameter. This of course make sense because our business requirement states the directory picked by the user must be empty if it is to be valid. Directories in Java are represented by the File object.

#13 This property will be used to hold a list of directory names excluded from the validator.

#16 The initialize method is part of the ConstraintValidator interface and can be overridden to do custom initialization of the validator.

#17 Here the excludes() method is called to get the String[] holding the names of the directories to exclude from validation. The excludes element may or may not be defined so the validator must handle both cases.  If the excludes element is defined and the excludes() method call returns a String[] of directory names to be excluded from validation, then the validator must ensure it does not validate those directories. If the excludes element is not defined and the excludes() method call returns nothing, then the validator should not exclude any directory from validation.

#25 The isValid method is part of the ConstraintValidator interface. This is where the magic happens. Code this method to perform any validation you need. Return true if valid, false otherwise.

#27 The code checks the excludes property to see if the list contains the name of the directory being validated. If the list does contain the name, then return true (#28) because that directory should be excluded from validation which means it is automatically valid.

#30 Use the File#listFiles method to get a list of all files and sub-directories. If there are files or sub-directories (#31) return false because the directory the user selected is not empty and fails the business requirement. Otherwise, return true (#34) because the directory the user selected is empty and this satisfies the business requirement.

You've learned how to create your own validation constraint annotation and its associated validator. You've also learned how easily the bean validation framework is extended and hopefully you recognize the great opportunities this gives you.

References
Bernard, Emmanuel. (2013, April). Bean Validation specification. beanvalidation.org. Retrieved January 21, 2015, from http://beanvalidation.org/1.1/spec/

JSR 349: Bean Validation 1.1. (n.d.). Java Community Process. Retrieved January 21, 2015, from https://jcp.org/en/jsr/detail?id=349

Enjoy!