Skip to content

Avoid nested constructor data binding if there are no request parameters #31821

Closed
@flpa

Description

@flpa

Note: I also raised this topic as a SO question: https://stackoverflow.com/q/77645476/827950

When attempting to upgrade our Spring Boot + Spring Web based REST API from Spring Boot 3.1.6 to 3.2.0, we encountered an unexpected change of behavior in databinding of query parameters. In some cases it leads to errors, in others it might cause tricky bugs.

The following example shows our situation: a REST controller with a simple GET endpoint that retrieves its parameters as an object that contains a nested object:

@RestController
@RequestMapping("/builder")
public class DemoLombokDataBuilder {

   @Data
   @Builder
   static class Dto {
      String field;
      Nested nested;
   }

   @Data
   static class Nested {
      String nestedField;
   }

   @GetMapping
   public String demo(Dto dto) {
      String result = dto.getField();

      if (dto.getNested() != null)
         result += dto.getNested().getNestedField();
      else
         result += ", nested is empty";

      return result;
   }

With Spring Boot 3.1.6, the result of calling the endpoint with or without the nested field are:

URL Result
http://localhost:8080/builder?field=foo foo, nested is empty
http://localhost:8080/builder?field=foo&nested.nestedField=bar foobar

With Spring Boot 3.2.0 however, the results are:

URL Result
http://localhost:8080/builder?field=foo foonull
http://localhost:8080/builder?field=foo&nested.nestedField=bar foobar

So instead of not instantiating the nested object at all when its field is not provided, Spring Boot 3.2 instantiates the nested object with empty values. This can lead to bugs - and it's even worse when validation is used and the nested object has mandatory fields annotated with @NotNull. In that case Spring Boot 3.2 throws HTTP 400, while the requests worked fine in 3.1.

To us, this seems like a change between minor version that can cause existing code to break, and therefore a bug. Is this expected behavior or did we miss something from the docs or upgrade guides?

Additional Details

In my further tests, the issue only occurs when the DTO class can only be constructed via an all-args-constructor.
With this DTO:

   @Data
   static class Dto {

      String field;
      Nested nested;
   }

both 3.1 and 3.2 have the same results:

URL Result
http://localhost:8080/builder?field=foo foo, nested is empty
http://localhost:8080/builder?field=foo&nested.nestedField=bar foobar

I pushed the full examples to this Github repo: https://github.com/flpa/springboot-databinder-change-tests

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: regressionA bug that is also a regression

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions