Jelena Borotic opened SPR-16922 and commented
I use Spring and Bean validation to validate request body in controllers. In case request is OK, program behaves normally, but if there is some constraint violation, I get InvalidPropertyException
:
org.springframework.beans.InvalidPropertyException: Invalid property 'offices[0]' of bean class [com.infobip.ott.sender.transactional.validationTest.Company]: Illegal attempt to get property 'offices' threw exception; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'offices[0]' of bean class [com.infobip.ott.sender.transactional.validationTest.Company]: Property referenced in indexed property path 'offices[0]' is neither an array nor a List nor a Set nor a Map; returned value was [Optional[[com.infobip.ott.sender.transactional.validationTest.Address@573a2ea7]]]org.springframework.beans.InvalidPropertyException: Invalid property 'offices[0]' of bean class [com.infobip.ott.sender.transactional.validationTest.Company]: Illegal attempt to get property 'offices' threw exception; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'offices[0]' of bean class [com.infobip.ott.sender.transactional.validationTest.Company]: Property referenced in indexed property path 'offices[0]' is neither an array nor a List nor a Set nor a Map; returned value was [Optional[[com.infobip.ott.sender.transactional.validationTest.Address@573a2ea7]]] at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:707) at org.springframework.beans.AbstractNestablePropertyAccessor.getNestedPropertyAccessor(AbstractNestablePropertyAccessor.java:839) at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyAccessorForPropertyPath(AbstractNestablePropertyAccessor.java:816) at org.springframework.beans.AbstractNestablePropertyAccessor.getPropertyValue(AbstractNestablePropertyAccessor.java:610) at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:104) at org.springframework.validation.AbstractBindingResult.getRawFieldValue(AbstractBindingResult.java:284) at org.springframework.validation.beanvalidation.SpringValidatorAdapter.getRejectedValue(SpringValidatorAdapter.java:296) at org.springframework.validation.beanvalidation.SpringValidatorAdapter.processConstraintViolations(SpringValidatorAdapter.java:152) at org.springframework.validation.beanvalidation.SpringValidatorAdapter.validate(SpringValidatorAdapter.java:119) at org.springframework.boot.autoconfigure.validation.ValidatorAdapter.validate(ValidatorAdapter.java:69) at org.springframework.validation.DataBinder.validate(DataBinder.java:871) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.validateIfApplicable(AbstractMessageConverterMethodArgumentResolver.java:260) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:136) at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:124) at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:161) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:131) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:865) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1655) at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:215) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642) at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642) at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:533) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:146) at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:257) at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1595) at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1564) at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144) at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:126) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) at org.eclipse.jetty.server.Server.handle(Server.java:531) at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:352) at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260) at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:281) at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:102) at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168) at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:132) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:760) at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:678) at java.base/java.lang.Thread.run(Thread.java:844)
Example that demonstrates the problem:
public class Address {
@Pattern(regexp = "[^\\d]*")
private final String street;
private final int number;
public Address(String street, int number) {
this.street = street;
this.number = number;
}
public String getStreet() {
return street;
}
public int getNumber() {
return number;
}
}
public class Company {
private final String name;
@Valid
@Size(min=2)
private final List<Address> offices;
public Company(String name, List<Address> offices) {
this.name = name;
this.offices = offices;
}
public String getName() {
return name;
}
public Optional<List<Address>> getOffices() {
return Optional.ofNullable(offices);
}
}
@RestController
public class ValidationController {
@PostMapping("/example/validation")
public Company createCompany(@RequestBody @Validated Company company) {
return company;
}
}
Exception is thrown in org.springframework.beans.AbstractNestablePropertyAccessor
in the getPropertyValue
method because passed object is nether List, Map, Set nor Array and due to that, can not be unwrapped.
We would like this class to be immutable. Initializing offices to an empty list in case of null would create problems with validation (due to minimum size constraint) and returning a non-optional list would reduce the readability and understanding of the code.
Introspector recognizes this property correctly (as an Optional). Since Optional is a class from standard Java library, and Spring 5 is baselined with Java 8, could it be supported as well?
No further details from SPR-16922
Comment From: spring-projects-issues
krueger commented
Hi Jelena,
can you provide the request body, please.
Comment From: flix-
I am facing the same issue... Minimal example to reproduce using Spring Boot 2.5.4 (Spring Framework 5.3.9).
Req:
public class Foo {
private Optional<List<@NotNull String>> ploink;
public Optional<List<String>> getPloink() {
return ploink;
}
public void setPloink(Optional<List<String>> ploink) {
this.ploink = ploink;
}
}
Request body:
{
"ploink": [null]
}
Comment From: baso53
This seems like a duplicate of Github issue #19935, or JIRA issue [SPR-15371].
Comment From: sbrannen
- duplicate of #19935