March 23rd, 2010

I found a little gotcha today after using the params parameter of Spring 3.0’s @RequestMapping annotation. The params parameter lets you map requests to methods in your Spring controller based on the value of HTTP parameters. I was implementing a wizard style set of forms today for the first time using Spring 3.0 and decided that this would fit really well. For example I could write something like:

    @RequestMapping(method = POST, params = "page=1")
    public String handlePage1(@Valid MyForm form, BindingResult result) {
       if (result.hasErrors()) {
           return "page1";
       }
       return "page2";
    }

We’re using Tomcat 6 as our servlet container and have previously found that to get it to handle non-ASCII characters correctly you need to set the character encoding on the request.  In Spring 3.0 world we’ve been doing this in our @InitBinder annotated method.

    @InitBinder
    public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
        request.setCharacterEncoding("UTF-8");
        // initialize the binder
    }

For some reason this wasn’t working in my new form: special characters submitted in the form weren’t being encoded properly.  This had me baffled for a while until I realized what the culprit was: my use of the params parameter with the @RequestMapping annotation!  After debugging it became apparent that using it causes HTTP request parameters to be parsed before the initBinder method is called and any further calls to request.getParameter() ignore the character encoding specified (possibly because the result is cached by the Tomcat implementation?).  This means I needed to set the character encoding before the HandlerAdaptor starts working on the @RequestMapping annotated methods.

Solution: set the character encoding via an interceptor

I managed to resolve this by creating a Spring interceptor (equivalent of a JEE Filter) to set the character encoding .

package com.example;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * Sets the character encoding for the request.  Defaults to UTF-8.
 */
public class SetCharacterEncodingInterceptor extends HandlerInterceptorAdapter {
    private String characterEncoding = "UTF-8";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        request.setCharacterEncoding(this.characterEncoding);
        return true;
    }

    public void setCharacterEncoding(String characterEncoding) {
        this.characterEncoding = characterEncoding;
    }
}

and then configuring Spring to use this interceptor for all my controllers:

    <mvc:interceptors>
        <bean class="com.example.SetCharacterEncodingInterceptor"/>
    </mvc:interceptors>

This meant I no longer need to set the encoding in my initBinder methods: removing duplicate code as a bonus:

    @InitBinder
    public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
        request.setCharacterEncoding("UTF-8");
        // initialize the binder
    }