4

I have a class:

@ColumnNameUnique(groups = CreateTableChecks.class)
public class Table {    
    @Valid
    @NotEmpty(groups = CreateTableChecks.class)
    private List<Measure> measures; 
}
  • The class level constraint @ColumnNameUnique(groups = CreateTableChecks.class) always runs first, after that the field level constraint @NotEmpty(groups = CreateTableChecks.class) runs.

  • Is there anyway to force the the field level constraint @NotEmpty(groups = CreateTableChecks.class) runs first?

Hardy
  • 18,659
  • 3
  • 49
  • 65
java_guy
  • 81
  • 2
  • 4

2 Answers2

2

You need to use @GroupSequence and re-define the default group sequence. Without this the validation order within a group is not defined and it can be in any order (that the class level constraint in your case is always executed first is not a must). Something like this should work:

@GroupSequence({FieldChecks.class, ClassChecks.class})
@ColumnNameUnique(groups = ClassChecks.class)
public class Table {    
    @Valid
    @NotEmpty(groups = FieldChecks.class)
    private List<Measure> measures; 
}

Now, if the @Default group gets validated, first the class level constraints and then the field level ones will be validated.

Hardy
  • 18,659
  • 3
  • 49
  • 65
  • 3
    This behavior should be default. If simple fields are not validated before the whole bean is validated we must either use a workaround like this, or recheck fields in class validator (check properties already marked as `@NotNull`). A bean can never be valid if validation fails on any single field. – djmj Jan 20 '17 at 00:34
  • 1
    I created an issue at https://hibernate.atlassian.net/browse/BVAL-557. The only generic and practical solution i came up with is to validate single properties using reflection first, instead of adding this workaround to all our entities. – djmj Jan 20 '17 at 01:06
  • Been awhile but I think there is a rather elegant solution for this annoying problem that I've detailed in the referenced issue https://hibernate.atlassian.net/browse/BVAL-557. Here's hoping someone there pays attention :). – Rene de Waele Aug 22 '18 at 22:47
-1

Instead of using @Hardy mentioned solution with @GroupSequence you can validate fields manually using reflection before your Validator.validate call.

Method

You can wrap this method

/**
 * Validates all single constrained fields of the given object and returns a
 * set of {@link ConstraintViolation}. If <code>first</code> is
 * <code>true</code> only the ConstraintViolation of the first invalid
 * constraint is returned. <br>
 * This method is useful to validate property constraints before class level
 * constraints.
 *
 * @param validator
 * @param object
 * @param first Set to <code>true</code> if only the exceptions of the first
 *            invalid property shall be thrown
 * @param groups
 */
public static Set<ConstraintViolation<Object>> validateProperties(final Validator validator, final Object object,
    final boolean first, final Class<?>... groups)
{
    if (object == null)
        throw new IllegalArgumentException("object must not be null.");
    if (validator == null)
        throw new IllegalArgumentException("validator must not be null.");

    final Set<ConstraintViolation<Object>> cvs = new HashSet<>();

    forFields: for (final Field field : ReflectionUtils.getAllFields(object.getClass(), null))
    {
        final Annotation[] annotations = field.getDeclaredAnnotations();

        boolean hasValidAnnotation = false;

        for (final Annotation annotation : annotations)
        {
            // single Constraint
            final Constraint constraint = annotation.annotationType().getAnnotation(Constraint.class);
            if (constraint != null)
            {
                cvs.addAll(validator.validateProperty(object, field.getName(), groups));

                if (!cvs.isEmpty() && first)
                    break forFields;
            }

            if (annotation.annotationType().equals(Valid.class))
                hasValidAnnotation = true;
        }

        // nested validation
        if (hasValidAnnotation)
        {
            field.setAccessible(true);
            Object value = null;
            try
            {
                value = field.get(object);
            }
            catch (IllegalArgumentException | IllegalAccessException e)
            {
                // log
            }

            if (value != null)
            {
                cvs.addAll(validateProperties(validator, value, first, groups));

                if (!cvs.isEmpty() && first)
                    break;

            }
        }
    }

    return cvs;
}

/**
 * Validates all single constrained fields of the given object and throws a
 * {@link ConstraintViolationException}. If <code>first</code> is
 * <code>true</code> only the ConstraintViolation of the first invalid
 * constraint is thrown. <br>
 * <br>
 * This method is useful to validate property constraints before class level
 * constraints.
 *
 * https://hibernate.atlassian.net/browse/BVAL-557
 *
 * @see #validateProperty(Validator, Object, String, Class...)
 *
 * @param validator
 * @param object
 * @param first Set to <code>true</code> if only the exceptions of the first
 *            invalid property shall be thrown
 * @param groups
 *
 * @throws ConstraintViolationException
 */
public static void validatePropertiesThrow(final Validator validator, final Object object, final boolean first,
    final Class<?>... groups) throws ConstraintViolationException
{
    if (object == null)
        throw new IllegalArgumentException("object must not be null.");
    if (validator == null)
        throw new IllegalArgumentException("validator must not be null.");

    final Set<ConstraintViolation<Object>> cvs = validateProperties(validator, object, first,
        groups);

    if (!cvs.isEmpty())
        throw new ConstraintViolationException(cvs);
}

I prefer this approach since i do not want to to update all our entities and fields with group sequence annotations.

djmj
  • 5,579
  • 5
  • 54
  • 92