0

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.

E-Riz
  • 31,431
  • 9
  • 97
  • 134

1 Answers1

1

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.

mark_o
  • 2,052
  • 1
  • 12
  • 18
  • Thanks, that works in my demonstration code , but my context is actually in Spring so I'll have to figure out how to register a custom extractor in Spring Boot. – E-Riz Oct 27 '22 at 15:30
  • [This answer](https://stackoverflow.com/a/64975661/639520) shows how to add a custom value extractor in Spring Boot. – E-Riz Oct 27 '22 at 15:48
  • Glad that you've found the answer to that! You can also try using the service loader mechanism if you'd want. Just create a `META-INF/services/javax.validation.valueextraction.ValueExtractor` file and add FQN value extractor class - see different options here -https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#section-valueextraction-registeringvalueextractor – mark_o Oct 27 '22 at 15:56