spring-hateoas
spring-hateoas copied to clipboard
Issues with DummyInvocationUtils on Java 17
I'm trying to move a project from Java 11 to Java 17 and I'm having issues with code that creates resource URLs.
linkTo(methodOn(EntityController.class)).getEntity(id).getHref()
The Controller method returns a CompletableFuture.
When I switch to Java 17, I get
java.lang.IllegalAccessException: module java.base does not open java.util.concurrent to unnamed module @6fdb1f78
at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:259) ~[na:na]
at java.base/jdk.internal.reflect.GeneratedMethodAccessor51.invoke(Unknown Source) ~[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:568) ~[na:na]
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:576) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.21.jar!/:5.3.21]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.hateoas.server.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:203) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
at org.springframework.hateoas.server.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:37) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
at org.springframework.hateoas.server.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.invoke(DummyInvocationUtils.java:96) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.example.EntityController$$EnhancerBySpringCGLIB$$8127ed24.getEntity(<generated>) ~[classes!/:na]
If I add --add-opens=java.base/java.util.concurrent=ALL-UNNAMED, I get
java.lang.IllegalArgumentException: $java.util.concurrent.CompletableFuture$$EnhancerBySpringCGLIB$$2c5f9ec4 not in same package as lookup class
at java.base/java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:167) ~[na:na]
at java.base/java.lang.invoke.MethodHandles$Lookup$ClassFile.newInstance(MethodHandles.java:2283) ~[na:na]
at java.base/java.lang.invoke.MethodHandles$Lookup.makeClassDefiner(MethodHandles.java:2318) ~[na:na]
at java.base/java.lang.invoke.MethodHandles$Lookup.defineClass(MethodHandles.java:1843) ~[na:na]
at java.base/jdk.internal.reflect.GeneratedMethodAccessor52.invoke(Unknown Source) ~[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:568) ~[na:na]
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:577) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.21.jar!/:5.3.21]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.hateoas.server.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:203) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
at org.springframework.hateoas.server.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:37) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
at org.springframework.hateoas.server.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.invoke(DummyInvocationUtils.java:96) ~[spring-hateoas-1.5.1.jar!/:1.5.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.21.jar!/:5.3.21]
at org.example.EntityController$$EnhancerBySpringCGLIB$$ee1b9f9d.getEntity(<generated>) ~[classes!/:na]
...
Please let me know if I can provide any more details.
Are you using Spring Framework / HATEOAS on the module path? Or does the error also appear in a plain classpath arrangement? A reproducer would be super helpful.
I'm not sure what you mean.
I'm using a Spring Boot REST application, I'm starting the fat jar with java -jar.
Here you go: demo.zip
Start the project using ./gradlew bootRun with Java 17 and call curl localhost:8080/help.
Please let me know if you need anything else.
For reference, the controller code in the demo project looks like this:
public class DemoController {
@GetMapping("/entity/{id}")
public CompletableFuture<Object> getEntity(@PathVariable String id) {
return CompletableFuture.completedFuture(Map.of("id", id));
}
@GetMapping("/help")
public Object getHelp() {
return Map.of("urls",
Map.of("entity", WebMvcLinkBuilder
// this is where the exception occurrs
.linkTo(WebMvcLinkBuilder.methodOn(DemoController.class).getEntity(null))
.withSelfRel().getHref()));
}
}
The stacktrace is
2022-06-29 14:34:38.143 ERROR 42435 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class java.util.concurrent.CompletableFuture: Common causes of this problem include using a final class or a non-visible class; nested exception is org.springframework.cglib.core.CodeGenerationException: java.lang.IllegalAccessException-->module java.base does not open java.util.concurrent to unnamed module @4cfaf581] with root cause
java.lang.IllegalAccessException: module java.base does not open java.util.concurrent to unnamed module @4cfaf581
at java.base/java.lang.invoke.MethodHandles.privateLookupIn(MethodHandles.java:259) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[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:568) ~[na:na]
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:576) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:110) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:108) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54) ~[spring-core-5.3.21.jar:5.3.21]
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:134) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.cglib.proxy.Enhancer.createClass(Enhancer.java:419) ~[spring-core-5.3.21.jar:5.3.21]
at org.springframework.aop.framework.ObjenesisCglibAopProxy.createProxyClassAndInstance(ObjenesisCglibAopProxy.java:57) ~[spring-aop-5.3.21.jar:5.3.21]
at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:206) ~[spring-aop-5.3.21.jar:5.3.21]
at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110) ~[spring-aop-5.3.21.jar:5.3.21]
at org.springframework.hateoas.server.core.DummyInvocationUtils.getProxyWithInterceptor(DummyInvocationUtils.java:203) ~[spring-hateoas-1.5.1.jar:1.5.1]
at org.springframework.hateoas.server.core.DummyInvocationUtils.access$000(DummyInvocationUtils.java:37) ~[spring-hateoas-1.5.1.jar:1.5.1]
at org.springframework.hateoas.server.core.DummyInvocationUtils$InvocationRecordingMethodInterceptor.invoke(DummyInvocationUtils.java:96) ~[spring-hateoas-1.5.1.jar:1.5.1]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.21.jar:5.3.21]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763) ~[spring-aop-5.3.21.jar:5.3.21]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-5.3.21.jar:5.3.21]
at com.example.demo.DemoController$$EnhancerBySpringCGLIB$$d4c33d56.getEntity(<generated>) ~[main/:na]
at com.example.demo.DemoController.getHelp(DemoController.java:25) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[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:568) ~[na:na]
...
I'm not sure what you mean. I'm using a Spring Boot REST application, I'm starting the fat jar with
java -jar.
That's helpful, thanks!
Here you go: demo.zip
Start the project using
./gradlew bootRunwith Java 17 and callcurl localhost:8080/help.Please let me know if you need anything else.
That, too! I'll have a look ASAP.
If I omit the CompletableFuture and return Map directly, it works fine.
Curiously, it also seems to work if I use Future instead of CompletableFuture in the method signature. :man_shrugging:
For interfaces, JDK proxies are generated. For concrete types, we need to create a class-based (CGLib) proxy, which fails as the JDK rejects a reflective reference to the core JDK type. We're currently investigating the issue internally. Can you switch to Future as the return type of the controller method as a workaround? Usually, those methods are not really called directly anyway.
I'll try that. I only just found that workaround myself. :wink:
Looks promising. I think I'll be able to work around the issue for us thanks to your quick and helpful response. Thanks! Feel free to close the issue.
Glad to hear that! I'll keep it around until we come to an official conclusion. Could be a fix or just some tweak to the documentation outlining the limitation or configuration flags to set when running the app to re-enable those types being proxied.
Hi @jochenberger,
If I add
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED, I get
@jhoeller suggested that you instead use --add-opens=java.base/java.lang=ALL-UNNAMED to open up the protected ClassLoader.defineClass(...) method used by Spring's CGLIB fork.
What happens if you instead do that?
@sbrannen, yes, that does seem to help. At least with the demo project. I didn't try the other one yet.