title: How to show a beanValidation class level constraint on a thymeleaf view?
body: <p>I'm using bean validation(JSR303) to validate two fields, those fields are validate with an annotation and a validator, the annotation targeted the type that means the annotation is a class level constraint of my bean.
  The question is how do I express the error on my view?
  (I'm using groovy)
  SignupForm :</p>
  
  <pre><code>package core.model
  
  import core.validation.PasswordConfirmation
  import core.validation.UniqueEmail
  import core.validation.UniqueUsername
  import groovy.util.logging.Slf4j
  import org.hibernate.validator.constraints.Email
  import org.hibernate.validator.constraints.NotBlank
  
  import static core.model.AuthoritiesEnum.ROLE_USER
  
  @Slf4j
  @PasswordConfirmation
  class SignupForm {
  
      static final String NOT_BLANK_MESSAGE = "{notBlank.message}"
      static final String UNIQUE_USERNAME_MESSAGE = "{uniqueUsername.message}"
      static final String EMAIL_MESSAGE = "{username.message}"
      static final String UNIQUE_EMAIL_MESSAGE = "{uniqueEmail.message}"
  
      @UniqueUsername
      @NotBlank(message = SignupForm.NOT_BLANK_MESSAGE)
      String username
  
      @UniqueEmail
      @NotBlank(message = SignupForm.NOT_BLANK_MESSAGE)
      @Email(message = SignupForm.EMAIL_MESSAGE)
      String email
  
      @NotBlank(message = SignupForm.NOT_BLANK_MESSAGE)
      String password
  
      @NotBlank(message = SignupForm.NOT_BLANK_MESSAGE)
      String confirmPassword
  
  
      User createAccount() {
          new User(username: email, email: email, password: password, role: ROLE_USER)
      }
  }
  </code></pre>
  
  <p>PasswordConfirmation validation annotation :</p>
  
  <pre><code>package core.validation
  
  import javax.validation.Constraint
  import javax.validation.Payload
  import java.lang.annotation.Retention
  import java.lang.annotation.Target
  
  import static java.lang.annotation.ElementType.TYPE
  import static java.lang.annotation.RetentionPolicy.RUNTIME
  
  @Target(TYPE)
  @Retention(RUNTIME)
  @Constraint(validatedBy = PasswordConfirmationValidator)
  @interface PasswordConfirmation {
  
      String message() default "{core.signup.validation.passwordConfirmation.message}"
  
      Class&lt;?&gt;[] groups() default []
  
      Class&lt;? extends Payload&gt;[] payload() default []
  }
  </code></pre>
  
  <p>Password confirmation validator :</p>
  
  <pre><code>package core.validation
  
  import core.model.SignupForm
  import groovy.util.logging.Slf4j
  
  import javax.validation.ConstraintValidator
  import javax.validation.ConstraintValidatorContext
  
  @Slf4j
  class PasswordConfirmationValidator implements ConstraintValidator&lt;PasswordConfirmation, SignupForm&gt; {
  
      @Override
      void initialize(PasswordConfirmation constraintAnnotation) {
      }
  
      @Override
      boolean isValid(SignupForm form, ConstraintValidatorContext context) {
          log.info "password : ${form.password}"
          log.info "confirmPassword : ${form.confirmPassword}"
          println "password : ${form.password}"
          println "confirmPassword : ${form.confirmPassword}"
          println "defaultConstraintMessageTemplate : ${context.defaultConstraintMessageTemplate}"
          form.password.equals form.confirmPassword
      }
  }
  </code></pre>
  
  <p>the thymeleaf view signup.html :</p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"&gt;
  
  &lt;head&gt;
      &lt;title th:text="#{view.signup.title}"&gt;Inscription&lt;/title&gt;
      &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/&gt;
      &lt;link href="../../../resources/css/bootstrap.min.css" rel="stylesheet" media="screen"
        th:href="@{/resources/css/bootstrap.min.css}"/&gt;
      &lt;link href="../../../resources/css/core.css" rel="stylesheet" media="screen" th:href="@{/resources/css/core.css}"/&gt;
      &lt;script src="http://code.jquery.com/jquery-latest.js"&gt;&lt;/script&gt;
      &lt;script src="../../../resources/js/bootstrap.min.js" th:src="@{/resources/js/bootstrap.min.js}"&gt;&lt;/script&gt;
  &lt;/head&gt;
  
  &lt;body&gt;
  
  &lt;div th:replace="fragments/header_signup :: header"&gt;Header&lt;/div&gt;
  &lt;form class="form-narrow form-horizontal" method="post"
    th:action="@{/signup}" th:object="${signupForm}"&gt;
      &lt;!-- /* Show general error message when form contains errors */ --&gt;
      &lt;th:block th:if="${#fields.hasErrors('${signupForm.*}')}"&gt;
          &lt;div th:replace="fragments/alert :: alert (type='danger', message='Form contains errors. Please try again.')"&gt;
              Alert
          &lt;/div&gt;
      &lt;/th:block&gt;
      &lt;fieldset&gt;
          &lt;div class="form-group" th:classappend="${#fields.hasErrors('username')}? 'has-error'"&gt;
              &lt;label for="username" class="col-lg-2 control-label"&gt;Username&lt;/label&gt;
  
              &lt;div class="col-lg-10"&gt;
                  &lt;input type="text" class="form-control" id="username" placeholder="Username" th:field="*{username}"/&gt;
                  &lt;span class="help-block" th:if="${#fields.hasErrors('username')}"
                    th:errors="*{username}"&gt;Incorrect username&lt;/span&gt;
              &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="form-group" th:classappend="${#fields.hasErrors('email')}? 'has-error'"&gt;
              &lt;label for="email" class="col-lg-2 control-label"&gt;Email&lt;/label&gt;
  
              &lt;div class="col-lg-10"&gt;
                  &lt;input type="text" class="form-control" id="email" placeholder="Email address" th:field="*{email}"/&gt;
                  &lt;span class="help-block" th:if="${#fields.hasErrors('email')}"
                    th:errors="*{email}"&gt;Incorrect email&lt;/span&gt;
              &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="form-group" th:classappend="${#fields.hasErrors('password')}? 'has-error'"&gt;
              &lt;label for="password" class="col-lg-2 control-label"&gt;Password&lt;/label&gt;
  
              &lt;div class="col-lg-10"&gt;
                  &lt;input type="password" class="form-control" id="password" placeholder="Password"
                     th:field="*{password}"/&gt;
                  &lt;span class="help-block" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"&gt;Incorrect password&lt;/span&gt;
              &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="form-group" th:classappend="${#fields.hasErrors('confirmPassword')}? 'has-error'"&gt;
              &lt;label for="confirmPassword" class="col-lg-2 control-label"&gt;Password confirmation&lt;/label&gt;
  
              &lt;div class="col-lg-10"&gt;
                  &lt;input type="password" class="form-control" id="confirmPassword" placeholder="Password confirmation"
                     th:field="*{confirmPassword}"/&gt;
                  &lt;!--how to show classe level constraint validation--&gt;
                  &lt;span class="help-block" th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}"&gt;Incorrect password confirmation&lt;/span&gt;
              &lt;/div&gt;
          &lt;/div&gt;
          &lt;div class="form-group"&gt;
              &lt;div class="col-lg-offset-2 col-lg-10"&gt;
                  &lt;button type="submit" class="btn btn-default" th:text="#{view.signup.label}"&gt;Sign up&lt;/button&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;/fieldset&gt;
  &lt;/form&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>