VRaptor

Validation

VRaptor validation uses Bean Validation, spec present in Java EE 7, that allow us to validate our beans based on annotations. With this you can use embedded constraints or create your custom constraint.

public class Client {
    // validates if name is not null and size between 10 and 50
    @NotNull @Size(min=10, max=50) private String name;

    // validates if birth date is past
    @Past private Date birth;
}

And in your controller:

public class ClientController {

    private final Validator validation;

    /** 
     * @deprecated CDI eyes only
     */
    protected ClientController() {
        this(null);
    }

    @Inject
    public ClientController(Validator validation) {
        this.validation = validation;
    }

    public void form() {
    }

    public void store(@NotNull @Valid Client client) {
        // if client is not valid, redirect to form page
        validation.onErrorForwardTo(this).form();
    }

}

Note: If your application uses Hibernate or JPA and you have your entities annotated with Bean Validation constraints, you need to call Session.flush() or EntityManager.flush() to validate your model before redirect or forward. The method flush synchronizes state from your objects to database. So Bean Validation constraint will only validate when flush is triggered.

Creating your custom constraints

With Bean validation you can create your own custom validations. If you want to validate if phone number is in (00) 0000-0000 pattern, you can do something like this:

@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Pattern(regexp = "\\((\\d{2})\\) (\\d{4})-(\\d{4})")
public @interface PhoneNumber {
    
    String message() default "{br.com.caelum.vraptor.validations.phonenumber.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

The message’s value with {} will be fetched from ResourceBundle.

Or you can put a fixed message, just like this:

String message() default  "Invalid number";

You can create complex constraints that access database or uses any managed component. If you want, as example, check if the user already exists in the database, ou can write a code like this:

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { LoginAvailableValidator.class })
@Documented
public @interface LoginAvailable {

    String message() default "{login_already_exists}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

And LoginAvailableValidator that checks the constraint:

public class LoginAvailableValidator implements ConstraintValidator<LoginAvailable, User> {

    @Inject
    private UserDao userDao;

    public boolean isValid(User user, ConstraintValidatorContext context) {
        return !userDao.containsUserWithLogin(user.getLogin());
    }
}

Using custom messages

If you prefer, you can use custom messages with Bean Validation. For this you need to add the file ValidationMessages.properties in your classpath. In a Mavem project, the location is /src/main/resources.

Considering the previous example, the file might have the following content to customize the default messages:

client.name.empty = The client name may not be empty
client.name.size = The client name size must be between 10 and 50
client.birth.past = The birth must be in the future

And the class:

public class Client {
    @NotNull(message="{client.name.empty}")
    @Size(min=10, max=50, message="{client.name.size}")
    private String name;

    @Past(message="{client.birth.past}")
    private Date birth;
}

The value {} present in annotations indicates that the message comes from the ResourceBundle.

See that was added parameters in order to let our most dynamic and reusable messages. The possibilities are currently:

Validation using Validator class methods

If you don’t want to use Bean Validation, you can use other methods located at Validator class. Below an example with add method:

if (client.getName() == null) {
  //an hard-coded message
  validator.add(new SimpleMessage("name", "The name is mandatory"));

  //i18n message
  validator.add(new I18nMessage("name", "name.mandatory"));
}

You can use another methods like addIf, that only adds the message if a precondition is true, and ensure that only adds the message if the precondition is false:

validator.addIf(client.getName() == null, new SimpleMessage("name", "The name is mandatory"));

validator.ensure(client.getName() != null, new SimpleMessage("name", "The name is mandatory"));

Redirecting when a constraint error occurs

When a constraint error occurs, you can redirect or forward the user to another page. You can see below some of the possible actions:

validator.onErrorForwardTo(MusicController.class).list() ==> validator.onErrorUse(logic()).forwardTo(MusicController.class).list();
validator.onErrorRedirectTo(MusicController.class).list() ==> validator.onErrorUse(logic()).redirectTo(MusicController.class).list();
validator.onErrorUsePageOf(MusicController.class).list() ==> validator.onErrorUse(page()).of(MusicController.class).list();
validator.onErrorSendBadRequest() ==> validator.onErrorUse(status()).badRequest(errors);

If you want to redirect to the same controller, you can use:

validator.onErrorForwardTo(this).list() ==> validator.onErrorUse(logic()).forwardTo(this.getClass()).list();
validator.onErrorRedirectTo(this).list() ==> validator.onErrorUse(logic()).redirectTo(this.getClass()).list();
validator.onErrorUsePageOf(this).list() ==> validator.onErrorUse(page()).of(this.getClass()).list();

When an error exists, VRaptor puts an attribute named errors in the request scope, that contains a list with all constraint errors. This list contains a key-value object that represents:

In the view you can do something like this:

<c:forEach var="error" items="${errors}">
    ${error.category} - ${error.message}<br />
</c:forEach>

You can search for an error for a specific category, that can be useful to display constraint the messages beside the field.

<input type="text" name="client.name" />
<span class="error">${errors.from('client.name')}</span>

It will print “may not be empty, size must be between 10 and 50”. You can also use the join method if you want to use another separator, just like this:

<span class="error">${errors.from('client.name').join(' - ')}</span>

That will print “may not be empty - size must be between 10 and 50”.

Adding more messages

When you add a message, the framework will add as an error. But its possible to add the message as success, info or warning. In this case you can simple use available attributes from Severity enum as you can see below:

validator.add(new SimpleMessage("client.name", "name field have accents", Severity.WARN));

And in the view you can use vmessages attribute:

<input type="text" name="client.name" />
<span class="error">${vmessages.warnings.from('client.name')}</span>

This code will print “name field have accents” beside the input text.

Programmatic validation

If you want, you can use the vraptor-simple-validator plugin to do your validations in a fluent way. It provide us a diversity of defaults validations that you may want to use.