3

Upgrading from Spring Boot 2.5.5 to 2.5.6 triggered errors of this type from my controller tests:

Spring Data REST controller WidgetController$$EnhancerBySpringCGLIB$$9dfdd90c_3 must not use @RequestMapping on class level as this would cause double registration with Spring MVC!

Caused by: java.lang.IllegalStateException: Spring Data REST controller WidgetController$$EnhancerBySpringCGLIB$$9dfdd90c_3 must not use @RequestMapping on class level as this would cause double registration with Spring MVC!
    at org.springframework.data.rest.webmvc.BasePathAwareHandlerMapping.isHandler(BasePathAwareHandlerMapping.java:165) ~[spring-data-rest-webmvc-3.5.6.jar:3.5.6]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.processCandidateBean(AbstractHandlerMethodMapping.java:265) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:225) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:213) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:206) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.restHandlerMapping(RepositoryRestMvcConfiguration.java:690) ~[spring-data-rest-webmvc-3.5.6.jar:3.5.6]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.12.jar:5.3.12]
    ... 88 common frames omitted

The controllers follow this pattern:

@RepositoryRestController
@RequestMapping("/api/widgets")
@RequiredArgsConstructor
public class WidgetController {
    private final @NotNull WidgetHandler widgetHandler;

    @GetMapping
    public ResponseEntity<List<Widget>> widgets() {
        return ResponseEntity.ok().body(widgetHandler./*...*/);
    }

    // ...
}
Marc Tarin
  • 3,109
  • 17
  • 49
Jan Nielsen
  • 10,892
  • 14
  • 65
  • 119
  • I have the same problem with @BasePathAwareController. Is there any solution other than modyfing paths for all methods? – Peters_ Nov 15 '21 at 14:53
  • I open an issue on SpringDataRest : https://github.com/spring-projects/spring-data-rest/issues/2087 – Yves Galante Nov 18 '21 at 17:15

2 Answers2

3

As from stacktrace, the exception is thrown by isHandler method from the BasePathAwareHandlerMapping class.

2.5.5

    protected boolean isHandler(Class<?> beanType) {
        Class<?> type = ProxyUtils.getUserClass(beanType);
        return type.isAnnotationPresent(BasePathAwareController.class);
    }

2.5.6

    protected boolean isHandler(Class<?> beanType) {
    ...
    if (AnnotatedElementUtils.hasAnnotation(type, RequestMapping.class)) {
        throw new IllegalStateException(String.format(AT_REQUEST_MAPPING_ON_TYPE, beanType.getName()));
    }
    ...
}

If you need the functionalities of spring data rest and you want to use @RepositoryRestController, you can use @RequestMapping (or related @GetMapping, @PostMapping, ...) at method level with the full path.

@RepositoryRestController
@RequiredArgsConstructor
public class WidgetController {
    private final @NotNull WidgetHandler widgetHandler;

    @GetMapping("/api/widgets")
    public ResponseEntity<List<Widget>> widgets() {
        return ResponseEntity.ok().body(widgetHandler./*...*/);
    }

// ...

}

Llednar
  • 31
  • 1
  • What if you have a lot of methods with the same base path? – Peters_ Nov 15 '21 at 13:20
  • 1
    You can use a 'static final String MODEL_PATH = "/myModel"' and pass it in each '@Mapping(value = MODEL_PATH + "/subpath")'. If you had an abstract CustomController class and you used to annotate shared methods only with the subpath and the extended 'MyController extends CustomController' with '@RequestMapping("/myModel")', you can no longer do it. Or better, i didn't find a way to make it work like was before yet. – Llednar Nov 16 '21 at 14:25
  • Thanks for the answer. Unfortunately I have also abstract classes... :) – Peters_ Nov 16 '21 at 14:40
  • Same situation here :\ the only viable alternative found for now is writing the shared methods in the superclass and then overriding them in the subclass calling super and annotating with the entrypoint. Obviously it is...ugly...at least for me...let me know if you find a better way! o/ – Llednar Nov 16 '21 at 14:48
  • 1
    Same issue ... I open a issue on SpringDataRest : https://github.com/spring-projects/spring-data-rest/issues/2087 – Yves Galante Nov 18 '21 at 17:14
1

Change @RepositoryRestController to @RestController:

@RestController @RequestMapping("/api/widgets") @RequiredArgsConstructor
public class WidgetController {
    private final @NotNull WidgetHandler widgetHandler;

    @GetMapping
    public ResponseEntity<List<Widget>> widgets() {
        return ResponseEntity.ok().body(widgetHandler./*...*/);
    }

    // ...
}
Jan Nielsen
  • 10,892
  • 14
  • 65
  • 119
  • There is difference between RepositoryRC and RC... Mainly it is not served under correct base url for example. see https://stackoverflow.com/questions/34512878/difference-between-restcontroller-and-repositoryrestcontroller/34518166 – Lubo Nov 23 '21 at 06:50