Skip to content

Spring MVC can wrongly return 405 http code when using annotated controllers with other handler mapping methods (router functions for example) #31563

Closed as not planned
@FBibonne

Description

@FBibonne

This issue stands for Spring framework 5.3.30 (spring-webmvc artifact) but the code seems to be the same in Spring 6

I use together annotated controllers and functional endpoints to handle http request in spring MVC and it happens that Spring MVC returns an 405 http code (Method Not Allowed) which is not expected.

For example : I declare an @PutMapping method in an annotated controller (class PersonController) to handle PUT /persons/{id} and I declare a functional endpoint (configuration class PersonConfig) to handle GET /persons/{id}. When I request GET /persons/{id}, the application returns a 405 http code (Method Not Allowed) because according to the annotated controller, the pattern /persons/{id} is only processed with PUT. That is not what is expected : when requested with GET /persons/{id} the application is expected to process the request with the method personHandler.

It happens because the implementation of handleNoMatch in RequestMappingInfoHandlerMapping seems to break the contract of AbstractHandlerMethodMapping.lookupHandlerMethod : when a request doen't match, handleNoMatch may raise an exception to explain why it doesn't match and so doesn't return null. Therefore lookupHandlerMethod doesn't return null as expected if no match. Then the DispatcherServlet stops searching in others handlerMapping if there is a match for the request.

In such a case, maybe exceptions like HttpRequestMethodNotSupportedException should be treated as null value returns and only raised if no other handlerMapping matches.

Class PersonConfig

@Configuration
public class PersonConfig {

    private static final String ID = "1";
    private static final String NAME = "Fabrice";
    private static final Person PERSON=new Person(ID, NAME);

    @Bean
    public RouterFunction<ServerResponse> personsGetters(){
        return route().path("/personsas", mapper->
                mapper.GET("/{id}", this::personHandler)
                        .GET(this::personHandler)).build();
    }

    public ServerResponse personHandler(ServerRequest req){
        if ((req.pathVariables().containsKey("id") && ID.equals(req.pathVariables().get("id")))
                || req.pathVariables().isEmpty()){
            return EntityResponse.fromObject(PERSON).build();
        }else{
            return ServerResponse.notFound().build();
        }

    }

}

Class PersonController

@RestController
@RequestMapping("/persons")
public class PersonController {

    @PutMapping("/{id}")
    public Person updatePerson(@RequestBody Person person, @PathVariable("id") String id){
        System.out.println("Update person with id "+id+" with "+person);
        return person;
    }

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: webIssues in web modules (web, webmvc, webflux, websocket)status: declinedA suggestion or change that we don't feel we should currently apply

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions