405 JSP error with Put Method

spring-mvc

Solution

The problem is that when you return a view name from your controller method, the Spring `DispatcherServlet` will do a forward to the given view, preserving the original `PUT` method.

On attempting to handle this forward, Tomcat will refuse it, with the justification that a `PUT` to a JSP could be construed to mean "replace this JSP file on the server with the content of this request."

Really you want your controller to handle your `PUT` requests and then to subsequently forward to your JSPs as `GET`. Fortunately Servlet 3.0 provides a means to filter purely on the `FORWARD` dispatcher.

Create a filter:

public class GetMethodConvertingFilter implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

        chain.doFilter(wrapRequest((HttpServletRequest) request), response);
    }

    @Override
    public void destroy() {
        // do nothing
    }

    private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
        return new HttpServletRequestWrapper(request) {
            @Override
            public String getMethod() {
                return "GET";
            }
        };
    }
}

And wire it into your `web.xml` thusly:

<filter>
    <filter-name>getMethodConvertingFilter</filter-name>
    <filter-class>my.GetMethodConvertingFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>getMethodConvertingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

This will convert requests to `GET` on forward only, leaving requests through other dispatchers unchanged, so `PUT`s will get intercepted by your controllers as normal.

My (possibly incorrect) understanding is that Tomcat 8.0.9 introduced a fix where this is effectively done automatically for the `ERROR` dispatcher - see the answer in your linked question. But you're not using the container's error handling mechanism to render your error page, you're using Spring MVC to manually forward to the view, hence why you need to do this. Personally I encountered this issue under Jetty 9.2.7 where no such fix is in place, and I delegate error handling to the container, so I have `<dispatcher>ERROR</dispatcher>` configured in my filter mapping as well.

This all seems a bit arcane but is the only way I've discovered to successfully jump through this particular RESTful-Spring-JSP-web-application hoop.

Problem

I'm hitting this issue regarding JSP not accepting PUT request. So I'm wondering how to fix it. I've read this related question in the stack overflow, but it doesn't explain how to fix it. HTTP Status 405 - JSPs only permit GET POST or HEAD Coming from Rails background, I'm trying to make it so I'm using the rails REST style like PUT for updates, and DELETE for deletes of the User resource. But whenever there's an error in this controller it tries to return the request to the originating JSP but with Tomcat 8.0.9 isn't accepting the request and gives this error: "HTTP Status 405 - JSPs only permit GET POST or HEAD". I've tried disabling readonly in the Tomcat web.xml - it didn't have any effect and I still get the error. I've switched it to POST method and the flow works fine. Is there a way to maybe force the forward to be POST method while still accepting PUT method for the request? ``` /** * Edit a user account. * @return the edit user view */ @RequestMapping(value = {"/update/{userId}"}, method = RequestMethod.PUT) public String updateUser(@Valid @ModelAttribute("user") User user, BindingResult result, final RedirectAttributes redirectAttributes) { logger.debug(user); // we check for duplicate email addresses during the update operation. List<User> userCheckList = userRepository.findByEmail(user.getEmail()); if (userCheckList.size() > 0) { // size of list should only ever be 1 User userCheck = userCheckList.get(0); if (userCheck.getId() != user.getId()) { result.rejectValue("email", "error.user", "An account already exists for this user email address."); } } if (result.hasErrors()) { return "admin.users.edit"; } // we locate the user and add it to the model userRepository.save(user); // the save operation was successful so we show the user message. redirectAttributes.addFlashAttribute("user", user); redirectAttributes.addFlashAttribute("message", "Updated successfully"); String viewName = "redirect:/admin/users"; logger.debug(viewName); return viewName; } ```

Original source

Related problems