After upgrading to Spring Boot 3.2.0-RC1 from 3.1.5 request params binding is not working.

@RestController
@RequestMapping
static class HelloController {
    enum Tag {RED, GREEN}

    record HelloArgs(String name, Set<Tag> tags) {
        HelloArgs(@RequestParam String name, @RequestParam Set<Tag> tags) {
            this.name = name;
            this.tags = tags == null ? Set.of() : tags;
        }
    }

    @GetMapping
    String hello(HelloArgs args) {
        return "Hello %s %s".formatted(args.name(), args.tags.stream().map(Tag::name).collect(Collectors.joining()));
    }
}

With request: http GET http://localhost:8080\?name\=Hulk

ERROR 54976 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.Set] with root cause

java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.Set
    at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:266) ~[spring-beans-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.validation.DataBinder.createObject(DataBinder.java:923) ~[spring-context-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.validation.DataBinder.createObject(DataBinder.java:952) ~[spring-context-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.validation.DataBinder.construct(DataBinder.java:902) ~[spring-context-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.bind.ServletRequestDataBinder.construct(ServletRequestDataBinder.java:112) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.constructAttribute(ServletModelAttributeMethodProcessor.java:156) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:148) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:217) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:170) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:918) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:830) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.15.jar:6.0]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.1.0-RC1.jar:6.1.0-RC1]
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.15.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.1.0-RC1.jar:6.1.0-RC1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:340) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.15.jar:10.1.15]
    at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

While with: http GET http://localhost:8080\?name\=Hulk\&tags\=RED,GREEN everything is okay.

Comment From: rstoyanchev

Thanks for giving the RC2 a try.

Both Spring Framework 6.0.x (Boot 3.1.x) and 6.1 (Boot 3.2) do not support constructor binding to a Collection. In the above example, 6.0.x passes null for the second parameter. 6.1 supports nested data constructors, and fails because it's trying to do more. We can make 6.1 ignore collection parameters, and pass null too, but I just confirm what your expectations are. Even in 6.0.x the tags parameter is not supported, it just happens to be more lenient by passing null.

As an aside, the @RequestParam annotations don't play any role at that level, unless you have some custom support for them. They're only checked when declared on controller method parameters.

Comment From: burl21

Thanks for the tip @RequestParam. If 6.1 is more strict, how should tags be declared?

Comment From: rstoyanchev

It's not more strict intentionally. We can adjust the behavior to be equally lenient and pass null. I just wasn't sure what role tags plays in the first place, it's not very useful if it's always null.