Use immutable objects in your Spring MVC controller by implementing your own WebArgumentResolver

by Tim van BaarsenDecember 8, 2011

How flexible is Spring MVC in combination with immutable objects? Why don’t we want Spring MVC decide for us how to build our objects used for binding? Curious how we tackled this problem? Read on!

In our current project we are using Spring MVC 3 to build our frond-end.
The binding mechanism of Spring MVC is very powerfull and flexible.
For example Spring MVC will automaticly bind fields from the request to the object you are using in your controller. But binding fields from the request to an object will only work when the class contains getters and setters.

An example
Our straightforward mutable address class:

public class MutableAddress {

    private String street;
    private String houseNumber;
    private String postalCode;
    private String city;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

   // Getters and setters for all other fields
}

The handler method on the controller takes a MutableAddress object (for example populated with data from the form) and saves it.

@Controller
public class AddressController {

   @RequestMapping(value = "/add", method = POST)
   public String storeAddress(MutableAddress mutableAddress) {
	addressService.storeAddress(mutableAddress);
	return "redirect:/overview";
   }
}

This all works fine but in our case we use immutable objects by design. So we don’t have any setters on our address class. The idea behind an immutable object is once it is created it will contain the correct values and cannot be changed anymore. So Spring MVC has some influence on the objects that we are using for binding data, but there is a way we can avoid that.

This is how the immutable version of our address class looks like:

public class ImmutableAddress {

    private final String street;
    private final String houseNumber;
    private final String postalCode;
    private final String city;

    public ImmutableAddress(String street, String houseNumber, String postalCode, String city) {
        Assert.hasText(street, "'street' must contain text");
        Assert.hasText(houseNumber, "'houseNumber' must contain text");
        Assert.hasText(postalCode, "'postalCode' must contain text");
        Assert.hasText(city, "'city' must contain text");

        this.street = street;
        this.houseNumber = houseNumber;
        this.postalCode = postalCode;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

   // All getters for the other fields. We don’t have setters!
}

The ImmutableAddress doesn’t contain a default constructor and can only be instantiated by passing in all parameters. Note that all fields are final and again we don’t have any setters! So out of the box using the ImmutableAddress in the method of our controller will not work.

Because we want to use ImmutableAddress directly in our controller method we have to do the binding part ourself. The best way to do this is to write a custom WebArgumentResolver.

public class ImmutableAddressWebArgumentResolver implements WebArgumentResolver {

    @Override
    public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) throws Exception {
        if (ImmutableAddress.class.equals(methodParameter.getParameterType())) {
            ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
            HttpServletRequest request = servletWebRequest.getRequest();

            String street = request.getParameter("street");
            String houseNumber = request.getParameter("houseNumber");
            String postalCode = request.getParameter("postalCode");
            String city = request.getParameter("city");
            return new ImmutableAddress(street, houseNumber, postalCode, city);
        }

        return WebArgumentResolver.UNRESOLVED;
    }
}

As you can see it’s quite easy. The only thing to do is implement a single method called “resolveArgument”. The implementation pulls out the parameters from the request and constructs a new ImmutableAddress that will be returned. This is only done when a ImmutableAddress is used as a parameter in one of our controller methods. In all other cases our custom WebArgumentResolver can’t resolve the arguments for the request so WebArgumentResolver.UNRESOLVED will be returned.

We have only one step left and that is to make sure our custom resolver kicks in before the default one of Spring MVC does.

Define our ImmutableAddressWebArgumentResolver as a Spring bean

<!-- Our custom argumentResolver -->
    <bean id="afnameIndentificatieArgumentResolver"
          class="nl.dutchworks.blog.web.binding.ImmutableAddressWebArgumentResolver"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
          p:customArgumentResolver-ref="immutableAddressWebArgumentResolver">
</bean>

Now we can use the ImmutableAddress directly in our controller

@RequestMapping(value = "/add-immutable", method = POST)
public String storeAddress(ImmutableAddress mutableAddress) {
	addressService.storeAddress(mutableAddress);
	return "redirect:/overview";
}

Make sure you don’t use:

<mvc:annotation-driven />

in your Spring context because we configured the AnnotationMethodHandlerAdapter in the context to make use of the customArgumentResolver.

At the moment of writing Spring 3.1 RC1 is available and you are able to use the mvc namespace to register your custom argument resolver:

<mvc:annotation-driven>
	<mvc:argument-resolvers>
		<bean class="nl.dutchworks.blog.web.binding.ImmutableAddressWebArgumentResolver"/>
	</mvc:argument-resolvers>
</mvc:annotation-driven>

My conclusion: there is no need to let Spring MVC decide the way you should build your objects for binding. And you don’t have to because Spring MVC is flexible enough just use WebArgumentResolvers!

To dive a little bit deeper in the code download the complete example here.