java – How do I prevent Spring 3.0 MVC @ModelAttribute variables from appearing in URL?-ThrowExceptions

Exception or error:

Using Spring MVC 3.0.0.RELEASE, I have the following Controller:

@Controller
@RequestMapping("/addIntake.htm")
public class AddIntakeController{

  private final Collection<String> users;

  public AddIntakeController(){
    users = new ArrayList<String>();
    users.add("user1");
    users.add("user2");
    // ...
    users.add("userN");
  }

  @ModelAttribute("users")
  public Collection<String> getUsers(){
    return this.users;
  }

  @RequestMapping(method=RequestMethod.GET)
  public String setupForm(ModelMap model){

    // Set up command object
    Intake intake = new Intake();
    intake.setIntakeDate(new Date());
    model.addAttribute("intake", intake);

    return "addIntake";
  }

  @RequestMapping(method=RequestMethod.POST)
  public String addIntake(@ModelAttribute("intake")Intake intake, BindingResult result){

    // Validate Intake command object and persist to database
    // ...

    String caseNumber = assignIntakeACaseNumber();

    return "redirect:intakeDetails.htm?caseNumber=" + caseNumber;

  }

}

The Controller reads Intake information from a command object populated from an HTML form, validates the command object, persists the information to the database, and returns a case number.

Everything works great, except for when I redirect to the intakeDetails.htm page, I get a URL that looks like this:

http://localhost:8080/project/intakeDetails.htm?caseNumber=1&users=user1&users=user2&users=user3&users=user4...

How do I prevent the user Collection from showing up in the URL?

How to solve:

Since spring 3.1 the RequestMappingHandlerAdapter provides a flag called ignoreDefaultModelOnRedirectthat you can use to prevent using the content of the defautl model if the controller redirects.

Answer´╝Ü

model.asMap().clear();
return "redirect:" + news.getUrl();

­čÖé

Answer´╝Ü

There are no good ways to solve this problem (i.e. without creating custom components, without excessive amounts of explicit xml configuration and without manual instantiation of RedirectView).

You can either instantiate RedirectView manually via its 4-argument constructor, or declare the following bean in your context (near other view resolvers):

public class RedirectViewResolver implements ViewResolver, Ordered {
    // Have a highest priority by default
    private int order = Integer.MIN_VALUE; 

    // Uses this prefix to avoid interference with the default behaviour
    public static final String REDIRECT_URL_PREFIX = "redirectWithoutModel:";     

    public View resolveViewName(String viewName, Locale arg1) throws Exception {
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            return new RedirectView(redirectUrl, true, true, false);
        }
        return null;
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}

Answer´╝Ü

The @ModelAttribute method annotation is intended to be used for exposing reference data to the view layer. I can’t say for sure in your case, but I wouldn’t say that a collection of users qualified as reference data. I suggest that you pass this information through to the model explicitly in your @RequestMapping-annotated handler methods.

If you still want to use @ModelAttribute, there’s a blog entry here that discusses the redirect problem.

But all the previous examples have a
common issue, as all @ModelAttribute
methods are run before the handler is
executed, if the handler returns a
redirect the model data will be added
to the url as a query string. This
should be avoided at all costs as it
could expose some secrets on how you
have put together your application.

His suggested solution (see part 4 of the blog) is to use a HandlerInterceptorAdapter to make the common reference data visible to the view. Since reference data shouldn’t be tightly coupled to the controllers, this shouldn’t pose a problem, design-wise.

Answer´╝Ü

I know this question and answer is old, but I stumbled upon it after having similar issues myself and there isn’t a lot of other info out there that I could find.

I think the accepted answer is not a very good one. The answer right below it by axtavt is much better. The question is not whether annotating model attributes on a controller makes sense. It’s about how to issue a “clean” redirect from within a controller that does normally use ModelAttributes. The controller itself normally requires the reference data, but sometimes it needs to redirect somewhere else for exceptional conditions or whatever, and passing the reference data doesn’t make sense. I think this a valid and common pattern.

(Fwiw, I ran in to this problem unexpectedly with Tomcat. Redirects were simply not working and I was getting odd error messages like: java.lang.ArrayIndexOutOfBoundsException: 8192. I eventually determined that Tomcat’s default max header length is 8192. I didn’t realize the ModelAttributes were being added automatically to the redirect URL, and that was causing the header length to exceed Tomcat’s max header length.)

Answer´╝Ü

I implemented a variant of Sid’s answer with less copying and pasting involved:

public class RedirectsNotExposingModelUrlBasedViewResolver extends UrlBasedViewResolver {

    @Override
    protected View createView(String viewName, Locale locale) throws Exception {
        View view = super.createView(viewName, locale);
        if (view instanceof RedirectView) {
            ((RedirectView) view).setExposeModelAttributes(false);
        }
        return view;
    }

}

This also requires a view resolver bean to be defined:

<bean id="viewResolver" class="com.example.RedirectsNotExposingModelUrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>

Answer´╝Ü

In my application I don’t have any use case for exposing model attributes in redirect so I’ve extended org.springframework.web.servlet.view.UrlBasedViewResolver to override the createView method and used declared in application context:

public class UrlBasedViewResolverWithouthIncludingModeAtttributesInRedirect extends   UrlBasedViewResolver {

        @Override
        protected View createView(String viewName, Locale locale) throws Exception {
            // If this resolver is not supposed to handle the given view,
            // return null to pass on to the next resolver in the chain.
            if (!canHandle(viewName, locale)) {
                return null;
            }
            // Check for special "redirect:" prefix.
            if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
                String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
                boolean exposeModelAttributes = false;
                return new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible(), exposeModelAttributes);
            }
            // Check for special "forward:" prefix.
            if (viewName.startsWith(FORWARD_URL_PREFIX)) {
                String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
                return new InternalResourceView(forwardUrl);
            }
            // Else fall back to superclass implementation: calling loadView.
            return super.createView(viewName, locale);
        }

}


  <bean id="viewResolver" class="com.acme.spring.UrlBasedViewResolverWithouthIncludingModeAtttributesInRedirect">

  </bean>

Answer´╝Ü

manually creating a RedirectView object worked for me:

@RequestMapping(method=RequestMethod.POST)
public ModelAndView addIntake(@ModelAttribute("intake")Intake intake, BindingResult result){

    // Validate Intake command object and persist to database
    // ...

    String caseNumber = assignIntakeACaseNumber();

    RedirectView rv = new RedirectView("redirect:intakeDetails.htm?caseNumber=" + caseNumber);
    rv.setExposeModelAttributes(false);
    return new ModelAndView(rv); 
}

IMHO this should be the default behavior when redirecting

Answer´╝Ü

Or, make that request a POST one. Get requests will only display the model attributes as request parameters appearing in the URL.

Answer´╝Ü

Here is how to do it with Java-based configuration (Spring 3.1+ I think, tested with 4.2):

@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {

    @Override
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
        RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter();
        adapter.setIgnoreDefaultModelOnRedirect(true);
        return adapter;
    }

    // possible other overrides as well

}

Answer´╝Ü

Don’t use @ModelAttribute. Store the users in the ModelMap explicitly. You’re doing as much with the command object anyway.

@RequestMapping(method=RequestMethod.GET)
    public String setupForm(ModelMap model){

        // Set up command object
        Intake intake = new Intake();
        intake.setIntakeDate(new Date());
        model.addAttribute("intake", intake);

        model.addAttribute("users", users);

        return "addIntake";
    }

The disadvantage to this is if a validation error takes place in addIntake(). If you want to simply return the logical name of the form, you must also remember to repopulate the model with the users, otherwise the form won’t be setup correctly.

Answer´╝Ü

There is a workaround if it helps your cause.

      @ModelAttribute("users")
      public Collection<String> getUsers(){
           return this.users;
      }

Here you have made it return Collection of String. Make it a Collection of User (it may be a class wrapping string representing a user, or a class with a bunch of data regarding a user). The problem happens with strings only. If the returned Collection contains any other object, this never happens. However, this is just a workaround, and may be, not required at all. Just my two cents. Just make it like –

      @ModelAttribute("users")
      public Collection<User> getUsers(){
           return this.users;
      }

Answer´╝Ü

try adding below code into the servlet-config.xml

<mvc:annotation-driven ignoreDefaultModelOnRedirect="true" />

sometimes this will solve the issue.

Leave a Reply

Your email address will not be published. Required fields are marked *