javahibernate-validatorjavax-validation

Why is validation property path appended with <list element>?


When using Hibernate Validator (via the javax.validation interfaces), I'm seeing a strange phenomenon in the property paths of nested objects when there are violations.

This demonstrates it:

@Documented
@Retention(RUNTIME)
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Constraint(validatedBy = AddressLineValidator.class)
public @interface ValidAddressLine {
    String message() default "Address line is invalid.";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

public class AddressLineValidator implements ConstraintValidator<ValidAddressLine, String> {
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && value.length() >= 5;
    }
}


public class ValidationPathTest {

    @lombok.Builder
    static class Person {
        private List<@Valid Address> addresses ;
    }

    @lombok.Builder
    static class Address {
        private List<@ValidAddressLine String> lines;
    }

    public static void main(String[] args) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Address address = Address.builder()
                            .lines(Arrays.asList("221 Baker St.", "B"))
                            .build();
        Person person = Person.builder()
                            .addresses(Arrays.asList(address))
                            .build();

        Set<ConstraintViolation<Person>> violations = validator.validate(person);
        violations.forEach(v -> System.out.println(v.getPropertyPath() + " : " + v.getMessage()));
    }
}

This program produces the output

addresses[0].lines[1].<list element> : Address line is invalid.

My question is, why is <list element> being appended to the property path, and only after the lines[1] node? The logical follow-up question is, is there some way I can stop or prevent it? Having this in the output out an API endpoint confuses the API users, and I'd like to eliminate it.


Solution

  • This comes from the BV specification on value extractors, see built-in value extractors section in particular:

    java.util.List; indexedValue() must be invoked for each contained element, passing the string literal as node name

    You probably could try to provide your own value extractor for the List. Something like:

    class CustomListValueExtractor implements ValueExtractor<List<@ExtractedValue ?>> {
        @Override
        public void extractValues(List<?> originalValue, ValueReceiver receiver) {
            for ( int i = 0; i < originalValue.size(); i++ ) {
                // not passing the node name, so it won't appear in the path
                receiver.indexedValue( null, i, originalValue.get( i ) );
            }
        }
    }
    

    and then pass it to the validator:

    Validator validator = Validation.byDefaultProvider().configure()
            .addValueExtractor( new CustomListValueExtractor() )
            .buildValidatorFactory()
            .getValidator();
    

    this should override the default extractor. But as this is not an intended behavior - please make sure you test that there are no issues caused by such change.