Search code examples
javaspringspring-mvcspring-form

Spring 5 immutable form to use an all arg constructor when there is No argument constructor as well


In an immutable class/object I have one no argument constructor initializing values to default/null, and another required argument constructor that initializes all the values to arguments from constructor.

When using the form binding (by specifying in request parameter in controller), spring is always calling the no argument constructor and not initializing the values. How can I ensure spring to call the required argument constructor only?

This is in spring version 5.1.5. I have tried adding @ConstructorProperties on the "required argument constructor" as well, but to no avail.


My immutable form/bean object:

public class ImmutableObj {
    private final Integer id;
    private final String name;

    // no arg constructor
    // spring calls this one when resolving request params
    public ImmutableObj() {
        this(null, null);
    }

    // required args constructor
    // I want spring to call this one when resolving request params
    @ConstructorProperies({"id", "name"})
    public ImmutableObj(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

And my controller:

@Controller
public class MyController {
    @GetMapping("myStuff")
    public String getMyStuff(ImmutableObj requestParams) {
        // here the value of request params
        // has nulls due to no arg constructor being called 
        return "someStuff";
    }
}

When calling "/myStuff?id=123&name=hello"

Expected - requestParams.getId()=123, requestParams.getName()=hello

Actual - requestParams.getId()=null, requestParams.getName()=null


UPDATE!!!!!!!!!!!!!

After removing the no arg constructor, I'm now running into issues for compositions:

public class ImmutableObj {
    private final SomeOtherObj someOtherObj;

    public ImmutableObj(SomeOtherObj obj) {
        someOtherObj = obj;
    }
}

public class SomeOtherObj {
    private final Integer id;
    private final String name;

    public SomeOtherObj(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

And spring throws:

Could not instantiate property type [SomeOtherObj] to auto-grow nested property path; nested exception is java.lang.NoSuchMethodException: SomeOtherObj.<init>()

Solution

  • Spring is always calling the no argument constructor and not initializing the values.

    When Spring see that the class has more than one constructor, it will go search for a no-argument one. If Spring doesn't find it, it will throw an exception.

    When Spring see that the class has exactly one constructor, it will take it regardless of a number of parameters it has.

    How can I ensure spring to call the required argument constructor only?

    The only way is to have only one constructor in the class. To make it unambiguous for Spring.

    As a side note,

    1. You don't need @ConstructorProperies({"id", "name"}) if the field names correspond to the URL parameter names. Spring can figure that out.

    2. public ImmutableObj() {
          this(null, null);
      }
      

    is not a good idea. ImmutableObj.empty() would be better.

    As a bonus, if you like to see what's going on behind the scenes, here's the snippet I was talking about

    if (ctor == null) {
      Constructor<?>[] ctors = clazz.getConstructors();
      if (ctors.length == 1) {
        ctor = ctors[0];
      } else {
        try {
          ctor = clazz.getDeclaredConstructor();
        } catch (NoSuchMethodException var10) {
          throw new IllegalStateException("No primary or default constructor found for " + clazz, var10);
        }
      }
    }