Affects: 6.0.0
I have a simple controller that goes like
@PostMapping
public ResponseEntity<?> insert(
@RequestBody @Valid CustomerApiDTO customerDTO) {
var customerEntity = customerMapper.map(customerDTO);
customerRepository.saveAndFlush(customerEntity);
var uri = MvcUriComponentsBuilder.fromMethodCall(
MvcUriComponentsBuilder.on(CustomerApiController.class)
.findByCustomerNumber(customerDTO.getCustomerNumber())
).build();
return ResponseEntity.created(uri.toUri()).build();
}
With AOT generating the proxy for the controller this fails with:
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController$$SpringCGLIB$$1.setCallbacks(<generated>)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder$ControllerMethodInvocationInterceptor.initProxy(MvcUriComponentsBuilder.java:814)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.controller(MvcUriComponentsBuilder.java:383)
Please let me know if I can provide any further help with this.
Please forgive: I am not sure where to file this issue as it goes with AOT.
Comment From: sbrannen
Can you provide the full stack trace?
Also, have you tried this with Spring Framework 6.0.0 (GA)?
Comment From: eiswind
I updated everything to spring boot 3-SNAPSHOT which pulls in 6.0.0. Still get the same error. Full stacktrace is
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1] with root cause
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController$$SpringCGLIB$$1.setCallbacks(<generated>)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder$ControllerMethodInvocationInterceptor.initProxy(MvcUriComponentsBuilder.java:814)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.controller(MvcUriComponentsBuilder.java:383)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on(MvcUriComponentsBuilder.java:358)
at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController.insert(CustomerApiController.java:105)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:699)
at de.eiswind.training.spring.rest.customer.responseentity.CustomerApiController$$SpringCGLIB$$0.insert(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:804)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:906)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:108)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
2022-11-16T15:15:15.722+01:00 ERROR 58498 --- [ main] o.s.t.w.reactive.server.ExchangeResult : Request details for assertion failure:
> POST http://localhost:43657/api/customer
> accept-encoding: [gzip]
> user-agent: [ReactorNetty/1.1.0]
> host: [localhost:43657]
> accept: [*/*]
> WebTestClient-Request-Id: [1]
> Content-Type: [application/json]
> Content-Length: [64]
{"customerNumber":"C001","firstname":"Test","lastname":"Mensch"}
< 500 INTERNAL_SERVER_ERROR Internal Server Error
< Content-Type: [application/json]
< Transfer-Encoding: [chunked]
< Date: [Wed, 16 Nov 2022 14:15:15 GMT]
< Connection: [close]
{"timestamp":"2022-11-16T14:15:15.695+00:00","status":500,"error":"Internal Server Error","path":"/api/customer"}
Comment From: eiswind
Please give me a minute, I'll try to set up a reproducible example.
Comment From: eiswind
That was a ride. I found that the failing proxy starts to be generated as soon as I have starter-hateoas on the classpath. removing it ends up with a different error, as the MvcUriComponentsBuilder then tries to generate a class at runtime.
Please have a quick look at
https://github.com/eiswind/mvcuri-native
where both failures are reproducible.
I did not find a way to register a cglib proxy manually yet. Is there one?
Comment From: odrotbohm
So, the non-HATEOAS arrangement basically stumbles over the fact that the controller class and the return type of the dummy method invocation need to be proxied, and no CGLib proxy classes have been registered for these types. I guess that's due to Spring MVC not yet providing any facilities to do that.
In the HATEOAS-world we have an AOT extension for WebMvcContollerLinkBuilder
to work properly (which predates MUCB
, basically does the same as that but produces Link
instances eventually). So, I assume that we now register e.g. a CGLib proxy with a certain proxy definition (declaring LastInvocationAware
as sole interface, a HATEOAS specific interceptor). This is done through a ProxyFactory
instance that, under the covers, uses the same APIs that MUCB
uses. When the code path in MUCB
is now hit, the HATEOAS-specific CGLib proxy is used, but in a different setup (registering MethodInvocationInfo
as interface and another dedicated interceptor), which then ultimately fails as it seems to try to add a second interceptor to the array already sized to 1.
I guess we'll have to consult the AOT team to avoid such conflicting proxy declarations.
Comment From: snicoll
@eiswind thanks for the report. The problem is that those proxies should be generated at build-time and the code in the middle of that controller method cannot be introspected. I've tried to trigger the creation of the proxy at build-time but this lead to:
Exception in thread "main" java.lang.IllegalStateException: No current ServletRequestAttributes
at org.springframework.util.Assert.state(Assert.java:76)
at org.springframework.web.servlet.support.ServletUriComponentsBuilder.getCurrentRequest(ServletUriComponentsBuilder.java:179)
at org.springframework.web.servlet.support.ServletUriComponentsBuilder.fromCurrentServletMapping(ServletUriComponentsBuilder.java:155)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.getBaseUrlToUse(MvcUriComponentsBuilder.java:559)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMethodInternal(MvcUriComponentsBuilder.java:539)
at org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMethodCall(MvcUriComponentsBuilder.java:321)
at com.example.mcvuri.MvcuriApplicationAotContribution$AotContribution.applyTo(MvcuriApplicationAotContribution.java:28)
At this stage, I don't know how this can be improved. We'll have to investigate.