Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClassNotFoundException with Caffeine and Spring Boot 3.2 #436

Open
sleicht opened this issue Jan 17, 2024 · 3 comments
Open

ClassNotFoundException with Caffeine and Spring Boot 3.2 #436

sleicht opened this issue Jan 17, 2024 · 3 comments
Labels
bug Something isn't working

Comments

@sleicht
Copy link

sleicht commented Jan 17, 2024

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 java.base@21.0.1/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 java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1346) ~[microservice:na]
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1335) ~[microservice:na]
        at java.base@21.0.1/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"));
    }
@sleicht sleicht added the bug Something isn't working label Jan 17, 2024
@sleicht
Copy link
Author

sleicht commented Jan 17, 2024

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"
        ]
      }
    ]
  }

@bpfoster
Copy link

bpfoster commented Feb 6, 2024

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.

@ben-manes
Copy link

ben-manes commented Feb 7, 2024

@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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants