Skip to content

WebDataBinder different behaviour between 6.0.x and 6.1.x - spring boot 3.0x and 3.2.xΒ #33279

Closed as not planned
@hugo-ma-alves

Description

@hugo-ma-alves

Hello

Description:
I'm investigating a possible bug with the Spring webDataBinder. Based on my understanding, this issue is reproducible on Spring Boot 3.2.x and 3.1.x, but not on Spring Boot 3.0.x. More specifically, the problem appears in Spring Web 6.1.x.

Sample Project: https://github.com/hugo-ma-alves/webbinder-bug

Related Issues:
This issue might be related to a previous one: #32919.

Expected behaviour

When invoking the following endpoint: http://localhost:8080/?%24page=50&%24size=1&name=example, it is expected that the SearchCriteria argument of the controller is populated with all the parameters sent in the URL, including $page and $size.

The controller should echo what it receives. The expected output is: example|50|1.

To map the $page and $size parameters, I created a binder (PaginationParametersBinder) that binds these parameters to the correct POJO property.

@InitBinder
public void bindCustomAttributeNames(WebDataBinder binder, WebRequest request) {
    Object objectToInspect = binder.getTarget();
    if (objectToInspect != null) {
        Class<?> classToInspect = objectToInspect.getClass();
        Map<String, String> paramAndInternalNameMap = buildRequestParamMapping(classToInspect);

        if (!paramAndInternalNameMap.isEmpty()) {
            TreeMap<String, String[]> requestParameterNamesAndValues = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            requestParameterNamesAndValues.putAll(request.getParameterMap());
            bindParameters(binder, paramAndInternalNameMap, requestParameterNamesAndValues);
        }
    }
}

Difference of behaviour between web 6.0.x and 6.1.x

On spring web 6.0.x the binder.getTarget(); returns a instance of my target pojo.
With this instance I can then invoke the binder.bind(propertyValues); and the values are correctly binded.
However, on web 6.1.x the binder.getTarget() returns null.
I refactored the code a bit to not rely on the target but just on the target class:

@InitBinder
public void bindCustomAttributeNames(WebDataBinder binder, WebRequest request) {
    Object objectToInspect = binder.getTarget();
// if (objectToInspect != null) {
        Class<?> classToInspect = binder.getTargetType().getRawClass();
        Map<String, String> paramAndInternalNameMap = buildRequestParamMapping(classToInspect);

        if (!paramAndInternalNameMap.isEmpty()) {
            TreeMap<String, String[]> requestParameterNamesAndValues = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            requestParameterNamesAndValues.putAll(request.getParameterMap());
            bindParameters(binder, paramAndInternalNameMap, requestParameterNamesAndValues);
        }
//  }
}

But this change will later cause the following error when invoking the .bind()

Caused by: java.lang.IllegalStateException: Cannot access properties on null bean instance 'searchCriteria'
	at org.springframework.validation.BeanPropertyBindingResult.createBeanWrapper(BeanPropertyBindingResult.java:111)
	at org.springframework.validation.BeanPropertyBindingResult.getPropertyAccessor(BeanPropertyBindingResult.java:97)
	at org.springframework.validation.DataBinder.getPropertyAccessor(DataBinder.java:378)
	at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:1220)
	at org.springframework.validation.DataBinder.doBind(DataBinder.java:1112)
	at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:235)
	at org.springframework.validation.DataBinder.bind(DataBinder.java:1087)
	at com.example.webbinderbug.PaginationParametersBinder.lambda$bindParameters$2(PaginationParametersBinder.java:41)
	at java.base/java.util.HashMap.forEach(HashMap.java:1429)
	at com.example.webbinderbug.PaginationParametersBinder.bindParameters(PaginationParametersBinder.java:35)
	at com.example.webbinderbug.PaginationParametersBinder.bindCustomAttributeNames(PaginationParametersBinder.java:28)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
	at org.springframework.web.method.annotation.InitBinderDataBinderFactory.initBinder(InitBinderDataBinderFactory.java:68)
	at org.springframework.web.bind.support.DefaultDataBinderFactory.createBinderInternal(DefaultDataBinderFactory.java:105)
	at org.springframework.web.bind.support.DefaultDataBinderFactory.createBinder(DefaultDataBinderFactory.java:87)
	at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:146)
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:224)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:178)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)

Steps to Reproduce using a unit test

  1. Run the UserControllerTest test in the attached project.
  2. The test should pass.
  3. Change the version of spring-boot-starter-parent to 3.3.2 and rerun the test.
    This time, the test will fail because the $page and $size parameters are not bound to the POJO.

Can you please help to understand if this is a bug or a change of behaviour introduced by spring web 6.1.x?

Thank you

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)status: invalidAn issue that we don't feel is valid

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions