graalvm-reachability-metadata
graalvm-reachability-metadata copied to clipboard
ClassNotFoundException with Caffeine and Spring Boot 3.2
Hello! I use spring boot (v. 3.2.1) with com.github.ben-manes.caffeine
(v. 3.1.8). When I make native-image with default configuration of CaffeineCacheManager, I get this error:
Caused by: java.lang.IllegalStateException: SSSW
at com.github.benmanes.caffeine.cache.LocalCacheFactory.newFactory(LocalCacheFactory.java:114) ~[microservice:3.1.8]
at [email protected]/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[microservice:na]
at com.github.benmanes.caffeine.cache.LocalCacheFactory.loadFactory(LocalCacheFactory.java:97) ~[microservice:3.1.8]
at com.github.benmanes.caffeine.cache.LocalCacheFactory.newBoundedLocalCache(LocalCacheFactory.java:46) ~[microservice:3.1.8]
at com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache.<init>(BoundedLocalCache.java:3953) ~[microservice:3.1.8]
at com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalLoadingCache.<init>(BoundedLocalCache.java:4451) ~[na:na]
at com.github.benmanes.caffeine.cache.Caffeine.build(Caffeine.java:1073) ~[microservice:3.1.8]
at com.COMPANY.libraries.auth.service.KeycloakService.<init>(KeycloakService.java:72) ~[microservice:na]
at com.COMPANY.libraries.auth.service.KeycloakService__BeanDefinitions.lambda$getKeycloakServiceInstanceSupplier$0(KeycloakService__BeanDefinitions.java:19) ~[na:na]
at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:68) ~[microservice:6.1.2]
at org.springframework.util.function.ThrowingBiFunction.apply(ThrowingBiFunction.java:54) ~[microservice:6.1.2]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.lambda$get$2(BeanInstanceSupplier.java:206) ~[na:na]
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) ~[microservice:6.1.2]
at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) ~[microservice:6.1.2]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.invokeBeanSupplier(BeanInstanceSupplier.java:214) ~[na:na]
at org.springframework.beans.factory.aot.BeanInstanceSupplier.get(BeanInstanceSupplier.java:206) ~[na:na]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.obtainInstanceFromSupplier(DefaultListableBeanFactory.java:949) ~[microservice:6.1.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1216) ~[microservice:6.1.2]
... 18 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.github.benmanes.caffeine.cache.SSSW
at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122) ~[na:na]
at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86) ~[na:na]
at [email protected]/java.lang.Class.forName(DynamicHub.java:1346) ~[microservice:na]
at [email protected]/java.lang.Class.forName(DynamicHub.java:1335) ~[microservice:na]
at [email protected]/java.lang.invoke.MethodHandles$Lookup.findClass(MethodHandles.java:2869) ~[microservice:na]
at com.github.benmanes.caffeine.cache.LocalCacheFactory.newFactory(LocalCacheFactory.java:104) ~[microservice:3.1.8]
... 35 common frames omitted
I found similar problem here
If I understand this repo correct, it misses the metadata for Caffeine for version 3.1.8 and it misses class com.github.benmanes.caffeine.cache.SSSW
The application starts if I add following hint manually:
hints.reflection().registerType(
TypeReference.of("com.github.benmanes.caffeine.cache.SSSW"),
MemberCategory.PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS
This additional CaffeineTest
leads to the same exception:
@Test
void testRecordStats() {
LoadingCache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(Duration.ofMinutes(20))
.recordStats()
.build(key -> key.equals("Hello") ? "World" : "Universe");
assertThat(cache.get("Hello")).isEqualTo("World");
assertThat(cache.getAll(List.of("Hi", "Aloha"))).isEqualTo(Map.of("Hi", "Universe", "Aloha", "Universe"));
}
Therefore missing in reflect-config.json
:
{
"condition": {
"typeReachable": "com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalLoadingCache"
},
"name": "com.github.benmanes.caffeine.cache.SSSW",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"com.github.benmanes.caffeine.cache.Caffeine",
"com.github.benmanes.caffeine.cache.AsyncCacheLoader",
"boolean"
]
}
]
}
SSW
is not the only class, I received the same error on com.github.benmanes.caffeine.cache.SSMSA
. I bet there are a ton of classes to include here as Caffeine is building the class name dynamically based on cache parameters.
@bpfoster yeah, pre-AOT this was a neat trick because unloaded classes are basically free (just a little extra disk space). Thus, we could code generate the cache and the entry classes to minimize the memory footprint, e.g. only include the expiration timestamp if used. Using reflection avoids bloating the constant pool with a static mapping and that factory is cached making the cost a one-time class load followed by a map lookup and direct call to the constructor. All extremely cheap, hidden, classic JVM dynamism except now AOT peeks into the implementation and exposes this magic. There's not much we can do since it is breaking encapsulation and including all classes would bloat your native binary. I'm curious to see how Project Leyden will address this, as it might allow AOT for the main parts while not throwing out the JIT for dynamic parts.
You can try this example if you want to use the agent to discover the classes (it will output the reflect-config.json
which you can copy into your project).