Adam Chesney opened SPR-17055 and commented
Attached is a zipped up Spring Boot 2.1 SNAPSHOT web project showing the problem.
A method like this:
@JsonIgnore
@AssertTrue
private boolean isNameNotFoo(){
return name != null && !name.equalsIgnoreCase("foo");
}
In a nested element that appears in a List of items in a request object causes a 500 when it fails validation.
So this json is fine and returns 200:
{"things":[{"name":"bar"}]}
But this json:
{"things":[{"name":"foo"}]}
blows up with 500:
> Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: JSR-303 validated property 'things[0].nameNotFoo' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)] with root cause``org.springframework.beans.NotReadablePropertyException: Invalid property 'things[0].nameNotFoo' of bean class [com.example.validationfail.api.OuterThing]: Bean property 'things[0].nameNotFoo' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
> at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:622) ~[spring-beans-5.1.0.BUILD-SNAPSHOT.jar:5.1.0.BUILD-SNAPSHOT]
> at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:612) ~[spring-beans-5.1.0.BUILD-SNAPSHOT.jar:5.1.0.BUILD-SNAPSHOT]
> at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:104) ~[spring-context-5.1.0.BUILD-SNAPSHOT.jar:5.1.0.BUILD-SNAPSHOT]
> at org.springframework.validation.AbstractBindingResult.getRawFieldValue(AbstractBindingResult.java:284) ~[spring-context-5.1.0.BUILD-SNAPSHOT.jar:5.1.0.BUILD-SNAPSHOT]
Affects: 5.0.8
Attachments: - validation-fail.zip (101.23 kB)
Comment From: spring-projects-issues
Stéphane Nicoll commented
That arrangement is very weird as you're faking a property to run a custom validation. I am not sure why the binder is looking at a private method though.
Juergen Hoeller what do you think?
Comment From: spring-projects-issues
Adam Chesney commented
Stéphane Nicoll thanks for looking at this.
AFAIK @AssertTrue
javax.validation only works against properties so the method has to begin with is in order to be picked up by the validator. My testing confirms this also.
https://stackoverflow.com/questions/2853826/spring-validation-asserttrue
However... I have just tested some more and Spring doesn't fail if I make the method public - it correctly returns a 400. It's just slightly weird that this behavior differs depending on the level within the heirarchy. private is validation methods work fine at the top level.
So in short: workaround is simply to elevate visibility of the validation method from private to public. Not ideal but not a disaster either :)
Comment From: spring-projects-issues
Stéphane Nicoll commented
AFAIK
@AssertTrue
javax.validation only works against properties so the method has to begin with is in order to be picked up by the validator.
That is correct but that's not really what you're doing. You are creating a fake property for the only purpose of validation of another property. I think the binder should probably ignore your private method though. Let's see what Juergen has to say about that.
Comment From: spring-projects-issues
Adam Chesney commented
The provided example was just that... an example... in the real world we are using an @AssertTrue
method to validate the state of multiple properties. e.g. check one is not null and two and three are null OR check that one is null and two and three are not null etc.
Is there a better way to acheive this kind of validation?
Thanks.
Comment From: spring-projects-issues
Stéphane Nicoll commented
It doesn't matter what you're doing in the body of the method. If the arrangement is still the same, you're still faking a property for the purpose of custom validation.
Is there a better way to acheive this kind of validation?
That's not really something we can debate here but for the record, this answer provides some information.
Comment From: spring-projects-issues
Adam Chesney commented
I was wrong - the behaviour is not different in the top level - private methods cause a 500 at all levels AFAICT
Comment From: tapioko
The mentioned stackoverflow answer solves this problem, but it requires two new classes and tens of lines of code. Using @AssertTrue is much simpler and it would be nice if it worked also in this case.
For the record this problem also occurs with directly nested objects, not just with objects nested within a list. And it doesn't seem to matter whether the validation is method is private or public.