Search code examples
javaspringspring-mvcspring-annotations

Inherited controller method without type parameter does not get mapped with RequestMapping


I created a base Spring controller for my latest web app project that I inherit from for all my basic CRUD controllers, called CrudController:

class CrudController<T, K extends Serializable>

T is the Entity type, and K is the key used for that type (pretty much the parameters needed for Spring's CrudRepository). I create a rest controller by extending it, like so:

@RestController
@RequestMapping(path = "/hats")
public class HatController extends CrudController<Hat, Integer> {
}

Everything works great, except for the method that gets the list of items, /hats. With this method, I get a 405 Method Not Allowed. When I look at the logging that was done during startup, I can see that RequestMappingHandlerMapping did not map {[/hats],methods=[GET]} but it did map the other four methods in the controller. Here is the code for the method that isn't being mapped:

@RequestMapping(method = RequestMethod.GET)
public HttpEntity<?> getAll() {
    Iterable<T> result = controllerRepository.findAll();
    return new ResponseEntity<Object>(result, HttpStatus.OK);
}

After much experimentation, I have discovered that if I add a parameter to the getAll method that is one of the class parameter types, the method will get mapped. Check this very similar code out:

@RequestMapping(method = RequestMethod.GET)
public HttpEntity<?> getAll(K dontuse) {
    Iterable<T> result = controllerRepository.findAll();
    return new ResponseEntity<Object>(result, HttpStatus.OK);
}

With that small code change, the /find call works fine. I can keep my dummy parameter in there and everything will work, but it smells.

Any ideas? A simplified project that replicates the issue can be found here:

Simple Project


Solution

  • There's currently a bug in Java, see bug reports here and here, where Class#getDeclaredMethods() returns a bridge Method for each inherited method from a superclass declared as package-private. Based on the javadoc, it shouldn't do that.

    This confuses Spring MVC's inspection of @Controller annotated classes and handler methods. Going into detail in this answer won't be very useful, but you can look at the code that handles it here.

    The simplest solution is to declare your CrudRepository class as public.

    public class CrudController<T, K extends Serializable>