Spring Data REST controller must not use @RequestMapping on class level as this would cause double registration with Spring MVC

Viewed 1344

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./*...*/);
    }

    // ...
}
2 Answers

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./*...*/);
    }

// ...

}

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./*...*/);
    }

    // ...
}
Related