javaelbean-validationcheck-constraints

How do I use EL to evaluate the length of an array in a Bean Validation constraint message?


I have a constraint on which I want to use EL to tune the message to the circumstances. Dependent on the length of an array, I want to show a different message. However, I'm having trouble getting the length of that array.

What am I doing wrong?

import org.junit.jupiter.api.Test;
import javax.validation.*;
import java.lang.annotation.*;
import static org.assertj.core.api.Assertions.assertThat;

public class FooTest {
    private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    @Test
    public void foo() {
        var violations = validator.validate(new ObjectWithFoo());
        assertThat(violations).extracting("message")
            .containsExactly("Field value should be foo");
    }

    @Test
    public void foos() {
        var violations = validator.validate(new ObjectWithFoos());
        assertThat(violations).extracting("message")
            .containsExactly("Field value should be one of [foo, bar, baz]");
    }

    @Foo(foos = {"foo"})
    private static class ObjectWithFoo{}

    @Foo(foos = {"foo", "bar", "baz"})
    private static class ObjectWithFoos{}

    @Constraint(validatedBy = FooValidator.class)
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Foo{
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};

        String[] foos();

        // This is the message I want to tune to the length of the array.
        // If the array contains just one element, I want to show a different message.
        // Note that 'one of foos' is a placeholder; I still need to figure out 
        // how to display the array in that case.
        String message() default "Field value should be ${foos.length == 1 ? foos[0] : 'one of foos'}";
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @interface List {
            Foo[] value();
        }
    }

    public static class FooValidator implements ConstraintValidator<Foo, Object> {
        @Override
        public void initialize(Foo constraintAnnotation) {
        }

        @Override
        public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
            return false; // for this test, we want the validation to fail
        }
    }
}

Unfortunately, this throws an exception:

20:03:52.810 [main] WARN org.hibernate.validator.internal.engine.messageinterpolation.ElTermResolver - 
HV000148: An exception occurred during evaluation of EL expression '${foos.length == 1 ? foos[0] : 'one of $foos'}'
java.lang.NumberFormatException: For input string: "length"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.base/java.lang.Integer.parseInt(Integer.java:652)

Solution

  • Thanks to the efforts of Thiago Henrique Hupner and Mark Thomas, support for a property of arrays called length has been added to EL 6 and up.