Description
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;
}
}